[
  {
    "path": ".github/workflows/build-on-push.yml",
    "content": "name: build-on-push\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\njobs:\n  build:\n    runs-on: windows-latest\n    permissions:\n      id-token: write\n      contents: read\n      attestations: write\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: setup msbuild\n        uses: microsoft/setup-msbuild@v2\n      - name: build debug\n        run:  msbuild msvc2022\\ui.sln -t:rebuild -verbosity:quiet -property:Configuration=debug -property:Platform=x64\n      - name: build release\n        run:  msbuild msvc2022\\ui.sln -t:rebuild -verbosity:quiet -property:Configuration=release -property:Platform=x64\n      - name: short sha\n        id: vars\n        run: echo \"sha_short=$(git rev-parse --short HEAD)\"  >> $GITHUB_OUTPUT\n      - name: check short sha\n        run: |\n            echo \"sha_short: $env:sha_short\"\n        env:\n          sha_short: ${{ steps.vars.outputs.sha_short }}\n      - name: run debug tests\n        run:  bin\\debug\\x64\\test1.exe --verbosity quiet\n      - name: run release tests\n        run:  bin\\release\\x64\\test1.exe --verbosity quiet\n      - name: Attest Build Provenance\n        uses: actions/attest-build-provenance@v1.1.2\n        with:\n          subject-path: 'bin\\**\\*.exe'\n      - name: upload release artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ui.release.zip\n          path: |\n            bin\\release\\**\\*.exe\n          retention-days: 5\n      - name: upload debug artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ui.debug.zip\n          path: |\n            bin\\debug\\**\\*.exe\n          retention-days: 5\n"
  },
  {
    "path": ".gitignore",
    "content": "# User files\n*.vcxproj.user\ninc/rt/version.h\nbin/**\nbuild/**\nlib/**\nmsvc2022/.vs/*\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Leo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ui\n\nSingle header libraries for building primitive UI/UX.\n\nWin32 only implementation for now. \nPosix/MacOS/iOS/Linux/Android - possible\n\n[![build-on-push](https://github.com/leok7v/ut/actions/workflows/build-on-push.yml/badge.svg)](https://github.com/leok7v/ut/actions/workflows/build-on-push.yml)\n\n# Two Namespaces:\n\n```\nrt_ for minimalistic fail fast runtime\nui_ for UI types and interfaces\n```\n\n# Minimalistic \"Hello\" Application Example\n\n```c\n#include \"ui/ui.h\"\n#include \"rt/rt.h\"\n\nstatic ui_label_t label = ui_label(0.0, \"Hello\");\n\nstatic void opened(void) {\n    ui_view.add(ui_app.view, &label, null);\n}\n\nstatic void init(void) {\n    ui_app.title = \"Sample\";\n    ui_app.opened = opened;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample\",\n    .init = init,\n    .window_sizing = {\n        .ini_w = 4.0f, // 4x2 inches\n        .ini_h = 2.0f\n    }\n};\n```\n"
  },
  {
    "path": "inc/generics.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// Most of ut/ui code is written the way of min(a,b) max(a,b)\n// not having side effects on the arguments and thus evaluating\n// them twice ain't a big deal. However, out of curiosity of\n// usefulness of Generic() in C11 standard here is type-safe\n// single evaluation of the arguments version of what could\n// have been simple minimum and maximum macro definitions.\n// Type safety comes with the cost of complexity in puritan\n// or stoic language like C:\n\nstatic inline int8_t   rt_max_int8(int8_t x, int8_t y)       { return x > y ? x : y; }\nstatic inline int16_t  rt_max_int16(int16_t x, int16_t y)    { return x > y ? x : y; }\nstatic inline int32_t  rt_max_int32(int32_t x, int32_t y)    { return x > y ? x : y; }\nstatic inline int64_t  rt_max_int64(int64_t x, int64_t y)    { return x > y ? x : y; }\nstatic inline uint8_t  rt_max_uint8(uint8_t x, uint8_t y)    { return x > y ? x : y; }\nstatic inline uint16_t rt_max_uint16(uint16_t x, uint16_t y) { return x > y ? x : y; }\nstatic inline uint32_t rt_max_uint32(uint32_t x, uint32_t y) { return x > y ? x : y; }\nstatic inline uint64_t rt_max_uint64(uint64_t x, uint64_t y) { return x > y ? x : y; }\nstatic inline fp32_t   rt_max_fp32(fp32_t x, fp32_t y)       { return x > y ? x : y; }\nstatic inline fp64_t   rt_max_fp64(fp64_t x, fp64_t y)       { return x > y ? x : y; }\n\n// MS cl.exe version 19.39.33523 has issues with \"long\":\n// does not pick up int32_t/uint32_t types for \"long\" and \"unsigned long\"\n// need to handle long / unsigned long separately:\n\nstatic inline long          rt_max_long(long x, long y)                    { return x > y ? x : y; }\nstatic inline unsigned long rt_max_ulong(unsigned long x, unsigned long y) { return x > y ? x : y; }\n\nstatic inline int8_t   rt_min_int8(int8_t x, int8_t y)       { return x < y ? x : y; }\nstatic inline int16_t  rt_min_int16(int16_t x, int16_t y)    { return x < y ? x : y; }\nstatic inline int32_t  rt_min_int32(int32_t x, int32_t y)    { return x < y ? x : y; }\nstatic inline int64_t  rt_min_int64(int64_t x, int64_t y)    { return x < y ? x : y; }\nstatic inline uint8_t  rt_min_uint8(uint8_t x, uint8_t y)    { return x < y ? x : y; }\nstatic inline uint16_t rt_min_uint16(uint16_t x, uint16_t y) { return x < y ? x : y; }\nstatic inline uint32_t rt_min_uint32(uint32_t x, uint32_t y) { return x < y ? x : y; }\nstatic inline uint64_t rt_min_uint64(uint64_t x, uint64_t y) { return x < y ? x : y; }\nstatic inline fp32_t   rt_min_fp32(fp32_t x, fp32_t y)       { return x < y ? x : y; }\nstatic inline fp64_t   rt_min_fp64(fp64_t x, fp64_t y)       { return x < y ? x : y; }\n\nstatic inline long          rt_min_long(long x, long y)                    { return x < y ? x : y; }\nstatic inline unsigned long rt_min_ulong(unsigned long x, unsigned long y) { return x < y ? x : y; }\n\n\nstatic inline void rt_min_undefined(void) { }\nstatic inline void rt_max_undefined(void) { }\n\n#define rt_max(X, Y) _Generic((X) + (Y), \\\n    int8_t:   rt_max_int8,   \\\n    int16_t:  rt_max_int16,  \\\n    int32_t:  rt_max_int32,  \\\n    int64_t:  rt_max_int64,  \\\n    uint8_t:  rt_max_uint8,  \\\n    uint16_t: rt_max_uint16, \\\n    uint32_t: rt_max_uint32, \\\n    uint64_t: rt_max_uint64, \\\n    fp32_t:   rt_max_fp32,   \\\n    fp64_t:   rt_max_fp64,   \\\n    long:     rt_max_long,   \\\n    unsigned long: rt_max_ulong, \\\n    default:  rt_max_undefined)(X, Y)\n\n#define rt_min(X, Y) _Generic((X) + (Y), \\\n    int8_t:   rt_min_int8,   \\\n    int16_t:  rt_min_int16,  \\\n    int32_t:  rt_min_int32,  \\\n    int64_t:  rt_min_int64,  \\\n    uint8_t:  rt_min_uint8,  \\\n    uint16_t: rt_min_uint16, \\\n    uint32_t: rt_min_uint32, \\\n    uint64_t: rt_min_uint64, \\\n    fp32_t:   rt_min_fp32,   \\\n    fp64_t:   rt_min_fp64,   \\\n    long:     rt_min_long,   \\\n    unsigned long: rt_min_ulong, \\\n    default:  rt_min_undefined)(X, Y)\n\n// The expression (X) + (Y) is used in _Generic primarily for type promotion\n// and compatibility between different types of the two operands. In C, when\n// you perform an arithmetic operation like addition between two variables,\n// the types of these variables undergo implicit conversions to a common type\n// according to the usual arithmetic conversions defined in the C standard.\n// This helps ensure that:\n//\n// Type Compatibility: The macro works correctly even if X and Y are of\n// different types. By using (X) + (Y), both X and Y are promoted to a common\n// type, which ensures that the macro selects the appropriate function\n// that can handle this common type.\n//\n// Type Safety: It ensures that the selected function can handle the type\n// resulting from the operation, thereby preventing type mismatches that\n// could lead to undefined behavior or compilation errors.\n\ntypedef struct {\n    void (*test)(void);\n} rt_generics_if;\n\nextern rt_generics_if rt_generics;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\" // must be first\n#include \"rt/rt_str.h\" // defines str_*_t types\n// the rest is in alphabetical order (no inter dependencies)\n#include \"rt/rt_args.h\"\n#include \"rt/rt_backtrace.h\"\n#include \"rt/rt_atomics.h\"\n#include \"rt/rt_clipboard.h\"\n#include \"rt/rt_clock.h\"\n#include \"rt/rt_config.h\"\n#include \"rt/rt_core.h\"\n#include \"rt/rt_debug.h\"\n#include \"rt/rt_files.h\"\n#include \"rt/rt_generics.h\"\n#include \"rt/rt_glyphs.h\"\n#include \"rt/rt_heap.h\"\n#include \"rt/rt_loader.h\"\n#include \"rt/rt_mem.h\"\n#include \"rt/rt_nls.h\"\n#include \"rt/rt_num.h\"\n#include \"rt/rt_static.h\"\n#include \"rt/rt_streams.h\"\n#include \"rt/rt_processes.h\"\n#include \"rt/rt_threads.h\"\n#include \"rt/rt_vigil.h\"\n#include \"rt/rt_work.h\"\n"
  },
  {
    "path": "inc/rt/rt_args.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct {\n    // On Unix it is responsibility of the main() to assign these values\n    int32_t c;      // argc\n    const char** v; // argv[argc]\n    const char** env; // rt_args.env[] is null-terminated\n    void    (*main)(int32_t argc, const char* argv[], const char** env);\n    void    (*WinMain)(void); // windows specific\n    int32_t (*option_index)(const char* option); // e.g. option: \"--verbosity\" or \"-v\"\n    void    (*remove_at)(int32_t ix);\n    /* argc=3 argv={\"foo\", \"--verbose\"} -> returns true; argc=1 argv={\"foo\"} */\n    bool    (*option_bool)(const char* option);\n    /* argc=3 argv={\"foo\", \"--n\", \"153\"} -> value==153, true; argc=1 argv={\"foo\"}\n       also handles negative values (e.g. \"-153\") and hex (e.g. 0xBADF00D)\n    */\n    bool    (*option_int)(const char* option, int64_t *value);\n    // for argc=3 argv={\"foo\", \"--path\", \"bar\"}\n    //     option_str(\"--path\", option)\n    // returns option: \"bar\" and argc=1 argv={\"foo\"} */\n    const char* (*option_str)(const char* option);\n    // basename() for argc=3 argv={\"/bin/foo.exe\", ...} returns \"foo\":\n    const char* (*basename)(void);\n    void (*fini)(void);\n    void (*test)(void);\n} rt_args_if;\n\nextern rt_args_if rt_args;\n\n/* Usage:\n\n    (both main() and WinMain() could be compiled at the same time on Windows):\n\n    static int run(void);\n\n    int main(int argc, char* argv[], char* envp[]) { // link.exe /SUBSYSTEM:CONSOLE\n        rt_args.main(argc, argv, envp); // Initialize args with command-line parameters\n        int r = run();\n        rt_args.fini();\n        return r;\n    }\n\n    #include \"rt/rt_win32.h\"\n\n    int APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, char* command, int show) {\n        // link.exe /SUBSYSTEM:WINDOWS\n        rt_args.WinMain();\n        int r = run();\n        rt_args.fini();\n        return 0;\n    }\n\n    static int run(void) {\n        if (rt_args.option_bool(\"-v\")) {\n            rt_debug.verbosity.level = rt_debug.verbosity.verbose;\n        }\n        int64_t num = 0;\n        if (rt_args.option_int(\"--number\", &num)) {\n            printf(\"--number: %ld\\n\", num);\n        }\n        const char* path = rt_args.option_str(\"--path\");\n        if (path != null) {\n            printf(\"--path: %s\\n\", path);\n        }\n        printf(\"rt_args.basename(): %s\\n\", rt_args.basename());\n        printf(\"rt_args.v[0]: %s\\n\", rt_args.v[0]);\n        for (int i = 1; i < rt_args.c; i++) {\n            printf(\"rt_args.v[%d]: %s\\n\", i, rt_args.v[i]);\n        }\n        return 0;\n    }\n\n    // Also see: https://github.com/leok7v/ut/blob/main/test/test1.c\n\n*/\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_atomics.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\n// Will be deprecated soon after Microsoft fully supports <stdatomic.h>\n\nrt_begin_c\n\ntypedef struct {\n    void* (*exchange_ptr)(volatile void** a, void* v); // retuns previous value\n    int32_t (*increment_int32)(volatile int32_t* a); // returns incremented\n    int32_t (*decrement_int32)(volatile int32_t* a); // returns decremented\n    int64_t (*increment_int64)(volatile int64_t* a); // returns incremented\n    int64_t (*decrement_int64)(volatile int64_t* a); // returns decremented\n    int32_t (*add_int32)(volatile int32_t* a, int32_t v); // returns result of add\n    int64_t (*add_int64)(volatile int64_t* a, int64_t v); // returns result of add\n    // returns the value held previously by \"a\" address:\n    int32_t (*exchange_int32)(volatile int32_t* a, int32_t v);\n    int64_t (*exchange_int64)(volatile int64_t* a, int64_t v);\n    // compare_exchange functions compare the *a value with the comparand value.\n    // If the *a is equal to the comparand value, the \"v\" value is stored in the address\n    // specified by \"a\" otherwise, no operation is performed.\n    // returns true if previous value *a was the same as \"comparand\"\n    // false if *a was different from \"comparand\" and \"a\" was NOT modified.\n    bool (*compare_exchange_int64)(volatile int64_t* a, int64_t comparand, int64_t v);\n    bool (*compare_exchange_int32)(volatile int32_t* a, int32_t comparand, int32_t v);\n    bool (*compare_exchange_ptr)(volatile void** a, void* comparand, void* v);\n    void (*spinlock_acquire)(volatile int64_t* spinlock);\n    void (*spinlock_release)(volatile int64_t* spinlock);\n    int32_t (*load32)(volatile int32_t* a);\n    int64_t (*load64)(volatile int64_t* a);\n    void (*memory_fence)(void);\n    void (*test)(void);\n} rt_atomics_if;\n\nextern rt_atomics_if rt_atomics;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_backtrace.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\n// \"bt\" stands for Stack Back Trace (not British Telecom)\n\nrt_begin_c\n\nenum { rt_backtrace_max_depth = 32 };    // increase if not enough\nenum { rt_backtrace_max_symbol = 1024 }; // MSFT symbol size limit\n\ntypedef struct thread_s* rt_thread_t;\n\ntypedef char rt_backtrace_symbol_t[rt_backtrace_max_symbol];\ntypedef char rt_backtrace_file_t[512];\n\ntypedef struct rt_backtrace_s {\n    int32_t frames; // 0 if capture() failed\n    uint32_t hash;\n    errno_t  error; // last error set by capture() or symbolize()\n    void* stack[rt_backtrace_max_depth];\n    rt_backtrace_symbol_t symbol[rt_backtrace_max_depth];\n    rt_backtrace_file_t file[rt_backtrace_max_depth];\n    int32_t line[rt_backtrace_max_depth];\n    bool symbolized;\n} rt_backtrace_t;\n\n//  calling .trace(bt, /*stop:*/\"*\")\n//  stops backtrace dumping at any of the known Microsoft runtime\n//  symbols:\n//    \"main\",\n//    \"WinMain\",\n//    \"BaseThreadInitThunk\",\n//    \"RtlUserThreadStart\",\n//    \"mainCRTStartup\",\n//    \"WinMainCRTStartup\",\n//    \"invoke_main\"\n// .trace(bt, null)\n// provides complete backtrace to the bottom of stack\n\ntypedef struct {\n    void (*capture)(rt_backtrace_t *bt, int32_t skip); // number of frames to skip\n    void (*context)(rt_thread_t thread, const void* context, rt_backtrace_t *bt);\n    void (*symbolize)(rt_backtrace_t *bt);\n    // dump backtrace into rt_println():\n    void (*trace)(const rt_backtrace_t* bt, const char* stop);\n    void (*trace_self)(const char* stop);\n    void (*trace_all_but_self)(void); // trace all threads\n    const char* (*string)(const rt_backtrace_t* bt, char* text, int32_t count);\n    void (*test)(void);\n} rt_backtrace_if;\n\nextern rt_backtrace_if rt_backtrace;\n\n#define rt_backtrace_here() do {   \\\n    rt_backtrace_t bt_ = {0};      \\\n    rt_backtrace.capture(&bt_, 0); \\\n    rt_backtrace.symbolize(&bt_);  \\\n    rt_backtrace.trace(&bt_, \"*\"); \\\n} while (0)\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_clipboard.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct ui_bitmap_s ui_bitmap_t;\n\ntypedef struct {\n    errno_t (*put_text)(const char* s);\n    errno_t (*get_text)(char* text, int32_t* bytes);\n    errno_t (*put_image)(ui_bitmap_t* image); // only for Windows apps\n    void (*test)(void);\n} rt_clipboard_if;\n\nextern rt_clipboard_if rt_clipboard;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_clock.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct {\n    int32_t const nsec_in_usec; // nano in micro second\n    int32_t const nsec_in_msec; // nano in milli\n    int32_t const nsec_in_sec;\n    int32_t const usec_in_msec; // micro in milli\n    int32_t const msec_in_sec;  // milli in sec\n    int32_t const usec_in_sec;  // micro in sec\n    fp64_t   (*seconds)(void);      // since boot\n    uint64_t (*nanoseconds)(void);  // since boot overflows in about 584.5 years\n    uint64_t (*unix_microseconds)(void); // since January 1, 1970\n    uint64_t (*unix_seconds)(void);      // since January 1, 1970\n    uint64_t (*microseconds)(void); // NOT monotonic(!) UTC since epoch January 1, 1601\n    uint64_t (*localtime)(void);    // local time microseconds since epoch\n    void (*utc)(uint64_t microseconds, int32_t* year, int32_t* month,\n        int32_t* day, int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms,\n        int32_t* mc);\n    void (*local)(uint64_t microseconds, int32_t* year, int32_t* month,\n        int32_t* day, int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms,\n        int32_t* mc);\n    void (*test)(void);\n} rt_clock_if;\n\nextern rt_clock_if rt_clock;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/rt/rt_config.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// Persistent storage for configuration and other small data\n// related to specific application.\n// on Unix-like system ~/.name/key files are used.\n// On Window User registry (could be .dot files/folders).\n// \"name\" is customary basename of \"rt_args.v[0]\"\n\ntypedef struct {\n    errno_t (*save)(const char* name, const char* key,\n                    const void* data, int32_t bytes);\n    int32_t (*size)(const char* name, const char* key);\n    // load() returns number of actual loaded bytes:\n    int32_t (*load)(const char* name, const char* key,\n                    void* data, int32_t bytes);\n    errno_t (*remove)(const char* name, const char* key);\n    errno_t (*clean)(const char* name); // remove all subkeys\n    void (*test)(void);\n} rt_config_if;\n\nextern rt_config_if rt_config;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/rt/rt_core.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct {\n    errno_t (*err)(void); // errno or GetLastError()\n    void (*set_err)(errno_t err); // errno = err or SetLastError()\n    void (*abort)(void);\n    void (*exit)(int32_t exit_code); // only 8 bits on posix\n    void (*test)(void);\n    struct {                                // posix\n        errno_t const access_denied;        // EACCES\n        errno_t const bad_file;             // EBADF\n        errno_t const broken_pipe;          // EPIPE\n        errno_t const device_not_ready;     // ENXIO\n        errno_t const directory_not_empty;  // ENOTEMPTY\n        errno_t const disk_full;            // ENOSPC\n        errno_t const file_exists;          // EEXIST\n        errno_t const file_not_found;       // ENOENT\n        errno_t const insufficient_buffer;  // E2BIG\n        errno_t const interrupted;          // EINTR\n        errno_t const invalid_data;         // EINVAL\n        errno_t const invalid_handle;       // EBADF\n        errno_t const invalid_parameter;    // EINVAL\n        errno_t const io_error;             // EIO\n        errno_t const more_data;            // ENOBUFS\n        errno_t const name_too_long;        // ENAMETOOLONG\n        errno_t const no_child_process;     // ECHILD\n        errno_t const not_a_directory;      // ENOTDIR\n        errno_t const not_empty;            // ENOTEMPTY\n        errno_t const out_of_memory;        // ENOMEM\n        errno_t const path_not_found;       // ENOENT\n        errno_t const pipe_not_connected;   // EPIPE\n        errno_t const read_only_file;       // EROFS\n        errno_t const resource_deadlock;    // EDEADLK\n        errno_t const too_many_open_files;  // EMFILE\n    } const error;\n} rt_core_if;\n\nextern rt_core_if rt_core;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_debug.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// debug interface essentially is:\n// vfprintf(stderr, format, va)\n// fprintf(stderr, format, ...)\n// with the additional convience:\n// 1. writing to system log (e.g. OutputDebugString() on Windows)\n// 2. always appending \\n at the end of the line and thus flushing buffer\n// Warning: on Windows it is not real-time and subject to 30ms delays\n//          that may or may not happen on some calls\n\ntypedef struct {\n    int32_t level; // global verbosity (interpretation may vary)\n    int32_t const quiet;    // 0\n    int32_t const info;     // 1 basic information (errors and warnings)\n    int32_t const verbose;  // 2 detailed diagnostic\n    int32_t const debug;    // 3 including debug messages\n    int32_t const trace;    // 4 everything (may include nested calls)\n} rt_verbosity_if;\n\ntypedef struct {\n    rt_verbosity_if verbosity;\n    int32_t (*verbosity_from_string)(const char* s);\n    // \"T\" connector for outside write; return false to proceed with default\n    bool (*tee)(const char* s, int32_t count); // return true to intercept\n    void (*output)(const char* s, int32_t count);\n    void (*println_va)(const char* file, int32_t line, const char* func,\n        const char* format, va_list va);\n    void (*println)(const char* file, int32_t line, const char* func,\n        const char* format, ...);\n    void (*perrno)(const char* file, int32_t line,\n        const char* func, int32_t err_no, const char* format, ...);\n    void (*perror)(const char* file, int32_t line,\n        const char* func, int32_t error, const char* format, ...);\n    bool (*is_debugger_present)(void);\n    void (*breakpoint)(void); // no-op if debugger is not present\n    errno_t (*raise)(uint32_t exception);\n    struct  {\n        uint32_t const access_violation;\n        uint32_t const datatype_misalignment;\n        uint32_t const breakpoint;\n        uint32_t const single_step;\n        uint32_t const array_bounds;\n        uint32_t const float_denormal_operand;\n        uint32_t const float_divide_by_zero;\n        uint32_t const float_inexact_result;\n        uint32_t const float_invalid_operation;\n        uint32_t const float_overflow;\n        uint32_t const float_stack_check;\n        uint32_t const float_underflow;\n        uint32_t const int_divide_by_zero;\n        uint32_t const int_overflow;\n        uint32_t const priv_instruction;\n        uint32_t const in_page_error;\n        uint32_t const illegal_instruction;\n        uint32_t const noncontinuable;\n        uint32_t const stack_overflow;\n        uint32_t const invalid_disposition;\n        uint32_t const guard_page;\n        uint32_t const invalid_handle;\n        uint32_t const possible_deadlock;\n    } exception;\n    void (*test)(void);\n} rt_debug_if;\n\n#define rt_println(...) rt_debug.println(__FILE__, __LINE__, __func__, \"\" __VA_ARGS__)\n\nextern rt_debug_if rt_debug;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_files.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// space for \"short\" 260 utf-16 characters filename in utf-8 format:\ntypedef struct rt_file_name_s { char s[1024]; } rt_file_name_t;\n\nenum { rt_files_max_path = (int32_t)sizeof(rt_file_name_t) }; // *)\n\ntypedef struct rt_file_s rt_file_t;\n\ntypedef struct rt_files_stat_s {\n    uint64_t created;\n    uint64_t accessed;\n    uint64_t updated;\n    int64_t  size; // bytes\n    int64_t  type; // device / folder / symlink\n} rt_files_stat_t;\n\ntypedef struct rt_folder_s {\n    uint8_t data[512]; // implementation specific\n} rt_folder_t;\n\ntypedef struct {\n    rt_file_t* const invalid; // (rt_file_t*)-1\n    // rt_files_stat_t.type:\n    int32_t const type_folder;\n    int32_t const type_symlink;\n    int32_t const type_device;\n    // seek() methods:\n    int32_t const seek_set;\n    int32_t const seek_cur;\n    int32_t const seek_end;\n    // open() flags\n    int32_t const o_rd; // read only\n    int32_t const o_wr; // write only\n    int32_t const o_rw; // != (o_rd | o_wr)\n    int32_t const o_append;\n    int32_t const o_create; // opens existing or creates new\n    int32_t const o_excl;   // create fails if file exists\n    int32_t const o_trunc;  // open always truncates to empty\n    int32_t const o_sync;\n    // known folders ids:\n    struct { // known folders:\n        int32_t const home;      // \"c:\\Users\\<username>\" or \"/users/<username>\"\n        int32_t const desktop;\n        int32_t const documents;\n        int32_t const downloads;\n        int32_t const music;\n        int32_t const pictures;\n        int32_t const videos;\n        int32_t const shared;    // \"c:\\Users\\Public\"\n        int32_t const bin;       // \"c:\\Program Files\" aka \"/bin\" aka \"/Applications\"\n        int32_t const data;      // \"c:\\ProgramData\" aka \"/var\" aka \"/data\"\n    } const folder;\n    errno_t (*open)(rt_file_t* *file, const char* filename, int32_t flags);\n    bool    (*is_valid)(rt_file_t* file); // checks both null and invalid\n    errno_t (*seek)(rt_file_t* file, int64_t *position, int32_t method);\n    errno_t (*stat)(rt_file_t* file, rt_files_stat_t* stat, bool follow_symlink);\n    errno_t (*read)(rt_file_t* file, void* data, int64_t bytes, int64_t *transferred);\n    errno_t (*write)(rt_file_t* file, const void* data, int64_t bytes, int64_t *transferred);\n    errno_t (*flush)(rt_file_t* file);\n    void    (*close)(rt_file_t* file);\n    errno_t (*write_fully)(const char* filename, const void* data,\n                           int64_t bytes, int64_t *transferred);\n    bool (*exists)(const char* pathname); // does not guarantee any access writes\n    bool (*is_folder)(const char* pathname);\n    bool (*is_symlink)(const char* pathname);\n    const char* (*basename)(const char* pathname); // c:\\foo\\bar.ext -> bar.ext\n    errno_t (*mkdirs)(const char* pathname); // tries to deep create all folders in pathname\n    errno_t (*rmdirs)(const char* pathname); // tries to remove folder and its subtree\n    errno_t (*create_tmp)(char* file, int32_t count); // create temporary file\n    errno_t (*chmod777)(const char* pathname); // and whole subtree new files and folders\n    errno_t (*symlink)(const char* from, const char* to); // sym link \"ln -s\" **)\n    errno_t (*link)(const char* from, const char* to); // hard link like \"ln\"\n    errno_t (*unlink)(const char* pathname); // delete file or empty folder\n    errno_t (*copy)(const char* from, const char* to); // allows overwriting\n    errno_t (*move)(const char* from, const char* to); // allows overwriting\n    errno_t (*cwd)(char* folder, int32_t count); // get current working dir\n    errno_t (*chdir)(const char* folder); // set working directory\n    const char* (*known_folder)(int32_t kf_id);\n    const char* (*bin)(void);  // Windows: \"c:\\Program Files\" Un*x: \"/bin\"\n    const char* (*data)(void); // Windows: \"c:\\ProgramData\" Un*x: /data or /var\n    const char* (*tmp)(void);  // temporary folder (system or user)\n    // There are better, native, higher performance ways to iterate thru\n    // folders in Posix, Linux and Windows. The following is minimalistic\n    // approach to folder content reading:\n    errno_t (*opendir)(rt_folder_t* folder, const char* folder_name);\n    const char* (*readdir)(rt_folder_t* folder, rt_files_stat_t* optional);\n    void (*closedir)(rt_folder_t* folder);\n    void (*test)(void);\n} rt_files_if;\n\n// *) rt_files_max_path is a compromise - way longer than Windows MAX_PATH of 260\n// and somewhat shorter than 32 * 1024 Windows long path.\n// Use with caution understanding that it is a limitation and where it is\n// important heap may and should be used. Do not to rely on thread stack size\n// (default: 1MB on Windows, Android Linux 64KB, 512 KB  on MacOS, Ubuntu 8MB)\n//\n// **) symlink on Win32 is only allowed in Admin elevated\n//     processes and in Developer mode.\n\nextern rt_files_if rt_files;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_glyphs.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\n// Square Four Corners https://www.compart.com/en/unicode/U+26F6\n#define rt_glyph_square_four_corners                    \"\\xE2\\x9B\\xB6\"\n\n// Circled Cross Formee\n// https://codepoints.net/U+1F902\n#define rt_glyph_circled_cross_formee                   \"\\xF0\\x9F\\xA4\\x82\"\n\n// White Large Square https://www.compart.com/en/unicode/U+2B1C\n#define rt_glyph_white_large_square                     \"\\xE2\\xAC\\x9C\"\n\n// N-Ary Times Operator https://www.compart.com/en/unicode/U+2A09\n#define rt_glyph_n_ary_times_operator                   \"\\xE2\\xA8\\x89\"\n\n// Heavy Minus Sign https://www.compart.com/en/unicode/U+2796\n#define rt_glyph_heavy_minus_sign                       \"\\xE2\\x9E\\x96\"\n\n// Heavy Plus Sign https://www.compart.com/en/unicode/U+2795\n#define rt_glyph_heavy_plus_sign                        \"\\xE2\\x9E\\x95\"\n\n// Clockwise Rightwards and Leftwards Open Circle Arrows with Circled One Overlay\n// https://www.compart.com/en/unicode/U+1F502\n// rt_glyph_clockwise_rightwards_and_leftwards_open_circle_arrows_with_circled_one_overlay...\n#define rt_glyph_open_circle_arrows_one_overlay         \"\\xF0\\x9F\\x94\\x82\"\n\n// Halfwidth Katakana-Hiragana Prolonged Sound Mark https://www.compart.com/en/unicode/U+FF70\n#define rt_glyph_prolonged_sound_mark                   \"\\xEF\\xBD\\xB0\"\n\n// Fullwidth Plus Sign https://www.compart.com/en/unicode/U+FF0B\n#define rt_glyph_fullwidth_plus_sign                    \"\\xEF\\xBC\\x8B\"\n\n// Fullwidth Hyphen-Minus https://www.compart.com/en/unicode/U+FF0D\n#define rt_glyph_fullwidth_hyphen_minus                 \"\\xEF\\xBC\\x8D\"\n\n\n// Heavy Multiplication X https://www.compart.com/en/unicode/U+2716\n#define rt_glyph_heavy_multiplication_x                 \"\\xE2\\x9C\\x96\"\n\n// Multiplication Sign https://www.compart.com/en/unicode/U+00D7\n#define rt_glyph_multiplication_sign                    \"\\xC3\\x97\"\n\n// Trigram For Heaven (caption menu button) https://www.compart.com/en/unicode/U+2630\n#define rt_glyph_trigram_for_heaven                     \"\\xE2\\x98\\xB0\"\n\n// (tool bar drag handle like: msvc toolbars)\n// Braille Pattern Dots-12345678  https://www.compart.com/en/unicode/U+28FF\n#define rt_glyph_braille_pattern_dots_12345678          \"\\xE2\\xA3\\xBF\"\n\n// White Square Containing Black Medium Square\n// https://compart.com/en/unicode/U+1F795\n#define rt_glyph_white_square_containing_black_medium_square \"\\xF0\\x9F\\x9E\\x95\"\n\n// White Medium Square\n// https://compart.com/en/unicode/U+25FB\n#define rt_glyph_white_medium_square                   \"\\xE2\\x97\\xBB\"\n\n// White Square with Upper Right Quadrant\n// https://compart.com/en/unicode/U+25F3\n#define rt_glyph_white_square_with_upper_right_quadrant \"\\xE2\\x97\\xB3\"\n\n// White Square with Upper Left Quadrant https://www.compart.com/en/unicode/U+25F0\n#define rt_glyph_white_square_with_upper_left_quadrant \"\\xE2\\x97\\xB0\"\n\n// White Square with Lower Left Quadrant https://www.compart.com/en/unicode/U+25F1\n#define rt_glyph_white_square_with_lower_left_quadrant \"\\xE2\\x97\\xB1\"\n\n// White Square with Lower Right Quadrant https://www.compart.com/en/unicode/U+25F2\n#define rt_glyph_white_square_with_lower_right_quadrant \"\\xE2\\x97\\xB2\"\n\n// White Square with Upper Right Quadrant https://www.compart.com/en/unicode/U+25F3\n#define rt_glyph_white_square_with_upper_right_quadrant \"\\xE2\\x97\\xB3\"\n\n// White Square with Vertical Bisecting Line https://www.compart.com/en/unicode/U+25EB\n#define rt_glyph_white_square_with_vertical_bisecting_line \"\\xE2\\x97\\xAB\"\n\n// (White Square with Horizontal Bisecting Line)\n// Squared Minus https://www.compart.com/en/unicode/U+229F\n#define rt_glyph_squared_minus                          \"\\xE2\\x8A\\x9F\"\n\n// North East and South West Arrow https://www.compart.com/en/unicode/U+2922\n#define rt_glyph_north_east_and_south_west_arrow        \"\\xE2\\xA4\\xA2\"\n\n// South East Arrow to Corner https://www.compart.com/en/unicode/U+21F2\n#define rt_glyph_south_east_white_arrow_to_corner       \"\\xE2\\x87\\xB2\"\n\n// North West Arrow to Corner https://www.compart.com/en/unicode/U+21F1\n#define rt_glyph_north_west_white_arrow_to_corner       \"\\xE2\\x87\\xB1\"\n\n// Leftwards Arrow to Bar https://www.compart.com/en/unicode/U+21E6\n#define rt_glyph_leftwards_white_arrow_to_bar           \"\\xE2\\x87\\xA6\"\n\n// Rightwards Arrow to Bar https://www.compart.com/en/unicode/U+21E8\n#define rt_glyph_rightwards_white_arrow_to_bar          \"\\xE2\\x87\\xA8\"\n\n// Upwards White Arrow https://www.compart.com/en/unicode/U+21E7\n#define rt_glyph_upwards_white_arrow                    \"\\xE2\\x87\\xA7\"\n\n// Downwards White Arrow https://www.compart.com/en/unicode/U+21E9\n#define rt_glyph_downwards_white_arrow                  \"\\xE2\\x87\\xA9\"\n\n// Leftwards White Arrow https://www.compart.com/en/unicode/U+21E4\n#define rt_glyph_leftwards_white_arrow                  \"\\xE2\\x87\\xA4\"\n\n// Rightwards White Arrow https://www.compart.com/en/unicode/U+21E5\n#define rt_glyph_rightwards_white_arrow                 \"\\xE2\\x87\\xA5\"\n\n// Upwards White Arrow on Pedestal https://www.compart.com/en/unicode/U+21EB\n#define rt_glyph_upwards_white_arrow_on_pedestal        \"\\xE2\\x87\\xAB\"\n\n// Braille Pattern Dots-678 https://www.compart.com/en/unicode/U+28E0\n#define rt_glyph_3_dots_tiny_right_bottom_triangle      \"\\xE2\\xA3\\xA0\"\n\n// Braille Pattern Dots-2345678 https://www.compart.com/en/unicode/U+28FE\n// Combining the two into:\n#define rt_glyph_dotted_right_bottom_triangle           \"\\xE2\\xA3\\xA0\\xE2\\xA3\\xBE\"\n\n// Upper Right Drop-Shadowed White Square https://www.compart.com/en/unicode/U+2750\n#define rt_glyph_upper_right_drop_shadowed_white_square \"\\xE2\\x9D\\x90\"\n\n// No-Break Space (NBSP)\n// https://www.compart.com/en/unicode/U+00A0\n#define rt_glyph_nbsp                                   \"\\xC2\\xA0\"\n\n// Word Joiner (WJ)\n// https://compart.com/en/unicode/U+2060\n#define rt_glyph_word_joiner                            \"\\xE2\\x81\\xA0\"\n\n// Zero Width Space (ZWSP)\n// https://compart.com/en/unicode/U+200B\n#define rt_glyph_zwsp                                   \"\\xE2\\x80\\x8B\"\n\n// Zero Width Joiner (ZWJ)\n// https://compart.com/en/unicode/u+200D\n#define rt_glyph_zwj                                    \"\\xE2\\x80\\x8D\"\n\n// En Quad\n// https://compart.com/en/unicode/U+2000\n#define rt_glyph_en_quad \"\\xE2\\x80\\x80\"\n\n// Em Quad\n// https://compart.com/en/unicode/U+2001\n#define rt_glyph_em_quad \"\\xE2\\x80\\x81\"\n\n// En Space\n// https://compart.com/en/unicode/U+2002\n#define rt_glyph_en_space \"\\xE2\\x80\\x82\"\n\n// Em Space\n// https://compart.com/en/unicode/U+2003\n#define rt_glyph_em_space \"\\xE2\\x80\\x83\"\n\n// Hyphen https://www.compart.com/en/unicode/U+2010\n#define rt_glyph_hyphen                                \"\\xE2\\x80\\x90\"\n\n// Non-Breaking Hyphen https://www.compart.com/en/unicode/U+2011\n#define rt_glyph_non_breaking_hyphen                   \"\\xE2\\x80\\x91\"\n\n// Fullwidth Low Line https://www.compart.com/en/unicode/U+FF3F\n#define rt_glyph_fullwidth_low_line                    \"\\xEF\\xBC\\xBF\"\n\n// #define rt_glyph_light_horizontal                     \"\\xE2\\x94\\x80\"\n// Light Horizontal https://www.compart.com/en/unicode/U+2500\n#define rt_glyph_light_horizontal                     \"\\xE2\\x94\\x80\"\n\n// Three-Em Dash https://www.compart.com/en/unicode/U+2E3B\n#define rt_glyph_three_em_dash                         \"\\xE2\\xB8\\xBB\"\n\n// Infinity https://www.compart.com/en/unicode/U+221E\n#define rt_glyph_infinity                              \"\\xE2\\x88\\x9E\"\n\n// Black Large Circle https://www.compart.com/en/unicode/U+2B24\n#define rt_glyph_black_large_circle                    \"\\xE2\\xAC\\xA4\"\n\n// Large Circle https://www.compart.com/en/unicode/U+25EF\n#define rt_glyph_large_circle                          \"\\xE2\\x97\\xAF\"\n\n// Heavy Leftwards Arrow with Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F818\n#define rt_glyph_heavy_leftwards_arrow_with_equilateral_arrowhead           \"\\xF0\\x9F\\xA0\\x98\"\n\n// Heavy Rightwards Arrow with Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81A\n#define rt_glyph_heavy_rightwards_arrow_with_equilateral_arrowhead          \"\\xF0\\x9F\\xA0\\x9A\"\n\n// Heavy Leftwards Arrow with Large Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81C\n#define rt_glyph_heavy_leftwards_arrow_with_large_equilateral_arrowhead     \"\\xF0\\x9F\\xA0\\x9C\"\n\n// Heavy Rightwards Arrow with Large Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81E\n#define rt_glyph_heavy_rightwards_arrow_with_large_equilateral_arrowhead    \"\\xF0\\x9F\\xA0\\x9E\"\n\n// CJK Unified Ideograph-5973: Kanji Onna \"Female\" https://www.compart.com/en/unicode/U+5973\n#define rt_glyph_kanji_onna_female                                          \"\\xE2\\xBC\\xA5\"\n\n// Leftwards Arrow https://www.compart.com/en/unicode/U+2190\n#define rt_glyph_leftward_arrow                                             \"\\xE2\\x86\\x90\"\n\n// Upwards Arrow https://www.compart.com/en/unicode/U+2191\n#define rt_glyph_upwards_arrow                                              \"\\xE2\\x86\\x91\"\n\n// Rightwards Arrow\n// https://www.compart.com/en/unicode/U+2192\n#define rt_glyph_rightwards_arrow                                           \"\\xE2\\x86\\x92\"\n\n// Downwards Arrow https://www.compart.com/en/unicode/U+2193\n#define rt_glyph_downwards_arrow                                            \"\\xE2\\x86\\x93\"\n\n// Thin Space https://www.compart.com/en/unicode/U+2009\n#define rt_glyph_thin_space                                                 \"\\xE2\\x80\\x89\"\n\n// Medium Mathematical Space (MMSP) https://www.compart.com/en/unicode/U+205F\n#define rt_glyph_mmsp                                                       \"\\xE2\\x81\\x9F\"\n\n// Three-Per-Em Space https://www.compart.com/en/unicode/U+2004\n#define rt_glyph_three_per_em                                               \"\\xE2\\x80\\x84\"\n\n// Six-Per-Em Space https://www.compart.com/en/unicode/U+2006\n#define rt_glyph_six_per_em                                                 \"\\xE2\\x80\\x86\"\n\n// Punctuation Space https://www.compart.com/en/unicode/U+2008\n#define rt_glyph_punctuation                                                \"\\xE2\\x80\\x88\"\n\n// Hair Space https://www.compart.com/en/unicode/U+200A\n#define rt_glyph_hair_space                                                 \"\\xE2\\x80\\x8A\"\n\n// Chinese \"jin4\" https://www.compart.com/en/unicode/U+58F9\n#define rt_glyph_chinese_jin4                                               \"\\xE5\\xA3\\xB9\"\n\n// Chinese \"gong\" https://www.compart.com/en/unicode/U+8D70\n#define rt_glyph_chinese_gong                                                \"\\xE8\\xB5\\xB0\"\n\n// https://www.compart.com/en/unicode/U+1F9F8\n#define rt_glyph_teddy_bear                                                 \"\\xF0\\x9F\\xA7\\xB8\"\n\n// https://www.compart.com/en/unicode/U+1F9CA\n#define rt_glyph_ice_cube                                                   \"\\xF0\\x9F\\xA7\\x8A\"\n\n// Speaker https://www.compart.com/en/unicode/U+1F508\n#define rt_glyph_speaker                                                    \"\\xF0\\x9F\\x94\\x88\"\n\n// Speaker with Cancellation Stroke https://www.compart.com/en/unicode/U+1F507\n#define rt_glyph_mute                                                       \"\\xF0\\x9F\\x94\\x87\"\n\n// TODO: this is used for Font Metric Visualization\n\n// Full Block https://www.compart.com/en/unicode/U+2588\n#define rt_glyph_full_block                             \"\\xE2\\x96\\x88\"\n\n// Black Square https://www.compart.com/en/unicode/U+25A0\n#define rt_glyph_black_square                           \"\\xE2\\x96\\xA0\"\n\n// the appearance of a dragon walking\n// CJK Unified Ideograph-9F98 https://www.compart.com/en/unicode/U+9F98\n#define rt_glyph_walking_dragon                         \"\\xE9\\xBE\\x98\"\n\n// possibly highest \"diacritical marks\" character (Vietnamese)\n// Latin Small Letter U with Horn and Hook Above https://www.compart.com/en/unicode/U+1EED\n#define rt_glyph_u_with_horn_and_hook_above             \"\\xC7\\xAD\"\n\n// possibly \"long descender\" character\n// Latin Small Letter Qp Digraph https://www.compart.com/en/unicode/U+0239\n#define rt_glyph_qp_digraph                             \"\\xC9\\xB9\"\n\n// another possibly \"long descender\" character\n// Cyrillic Small Letter Shha with Descender https://www.compart.com/en/unicode/U+0527\n#define rt_glyph_shha_with_descender                    \"\\xD4\\xA7\"\n\n// a\"very long descender\" character\n// Tibetan Mark Caret Yig Mgo Phur Shad Ma https://www.compart.com/en/unicode/U+0F06\n#define rt_glyph_caret_yig_mgo_phur_shad_ma             \"\\xE0\\xBC\\x86\"\n\n// Tibetan Vowel Sign Vocalic Ll https://www.compart.com/en/unicode/U+0F79\n#define rt_glyph_vocalic_ll                             \"\\xE0\\xBD\\xB9\"\n\n// https://www.compart.com/en/unicode/U+1F4A3\n#define rt_glyph_bomb \"\\xF0\\x9F\\x92\\xA3\"\n\n// https://www.compart.com/en/unicode/U+1F4A1\n#define rt_glyph_electric_light_bulb \"\\xF0\\x9F\\x92\\xA1\"\n\n// https://www.compart.com/en/unicode/U+1F4E2\n#define rt_glyph_public_address_loudspeaker \"\\xF0\\x9F\\x93\\xA2\"\n\n// https://www.compart.com/en/unicode/U+1F517\n#define rt_glyph_link_symbol \"\\xF0\\x9F\\x94\\x97\"\n\n// https://www.compart.com/en/unicode/U+1F571\n#define rt_glyph_black_skull_and_crossbones \"\\xF0\\x9F\\x95\\xB1\"\n\n// https://www.compart.com/en/unicode/U+1F5B5\n#define rt_glyph_screen \"\\xF0\\x9F\\x96\\xB5\"\n\n// https://www.compart.com/en/unicode/U+1F5D7\n#define rt_glyph_overlap \"\\xF0\\x9F\\x97\\x97\"\n\n// https://www.compart.com/en/unicode/U+1F5D6\n#define rt_glyph_maximize \"\\xF0\\x9F\\x97\\x96\"\n\n// https://www.compart.com/en/unicode/U+1F5D5\n#define rt_glyph_minimize \"\\xF0\\x9F\\x97\\x95\"\n\n// Desktop Window\n// https://compart.com/en/unicode/U+1F5D4\n#define rt_glyph_desktop_window \"\\xF0\\x9F\\x97\\x94\"\n\n// https://www.compart.com/en/unicode/U+1F5D9\n#define rt_glyph_cancellation_x \"\\xF0\\x9F\\x97\\x99\"\n\n// https://www.compart.com/en/unicode/U+1F5DF\n#define rt_glyph_page_with_circled_text \"\\xF0\\x9F\\x97\\x9F\"\n\n// https://www.compart.com/en/unicode/U+1F533\n#define rt_glyph_white_square_button \"\\xF0\\x9F\\x94\\xB3\"\n\n// https://www.compart.com/en/unicode/U+1F532\n#define rt_glyph_black_square_button \"\\xF0\\x9F\\x94\\xB2\"\n\n// https://www.compart.com/en/unicode/U+1F5F9\n#define rt_glyph_ballot_box_with_bold_check \"\\xF0\\x9F\\x97\\xB9\"\n\n// https://www.compart.com/en/unicode/U+1F5F8\n#define rt_glyph_light_check_mark \"\\xF0\\x9F\\x97\\xB8\"\n\n// https://compart.com/en/unicode/U+1F4BB\n#define rt_glyph_personal_computer \"\\xF0\\x9F\\x92\\xBB\"\n\n// https://compart.com/en/unicode/U+1F4DC\n#define rt_glyph_desktop_computer \"\\xF0\\x9F\\x93\\x9C\"\n\n// https://compart.com/en/unicode/U+1F4DD\n#define rt_glyph_printer \"\\xF0\\x9F\\x93\\x9D\"\n\n// https://compart.com/en/unicode/U+1F4F9\n#define rt_glyph_video_camera \"\\xF0\\x9F\\x93\\xB9\"\n\n// https://compart.com/en/unicode/U+1F4F8\n#define rt_glyph_camera \"\\xF0\\x9F\\x93\\xB8\"\n\n// https://compart.com/en/unicode/U+1F505\n#define rt_glyph_high_brightness \"\\xF0\\x9F\\x94\\x85\"\n\n// https://compart.com/en/unicode/U+1F506\n#define rt_glyph_low_brightness \"\\xF0\\x9F\\x94\\x86\"\n\n// https://compart.com/en/unicode/U+1F507\n#define rt_glyph_speaker_with_cancellation_stroke \"\\xF0\\x9F\\x94\\x87\"\n\n// https://compart.com/en/unicode/U+1F509\n#define rt_glyph_speaker_with_one_sound_wave \"\\xF0\\x9F\\x94\\x89\"\n\n// Right-Pointing Magnifying Glass\n// https://compart.com/en/unicode/U+1F50E\n#define rt_glyph_right_pointing_magnifying_glass \"\\xF0\\x9F\\x94\\x8E\"\n\n// Radio Button\n// https://compart.com/en/unicode/U+1F518\n#define rt_glyph_radio_button \"\\xF0\\x9F\\x94\\x98\"\n\n// https://compart.com/en/unicode/U+1F525\n#define rt_glyph_fire \"\\xF0\\x9F\\x94\\xA5\"\n\n// Gear\n// https://compart.com/en/unicode/U+2699\n#define rt_glyph_gear \"\\xE2\\x9A\\x99\"\n\n// Nut and Bolt\n// https://compart.com/en/unicode/U+1F529\n#define rt_glyph_nut_and_bolt \"\\xF0\\x9F\\x94\\xA9\"\n\n// Hammer and Wrench\n// https://compart.com/en/unicode/U+1F6E0\n#define rt_glyph_hammer_and_wrench \"\\xF0\\x9F\\x9B\\xA0\"\n\n// https://compart.com/en/unicode/U+1F53E\n#define rt_glyph_upwards_button \"\\xF0\\x9F\\x94\\xBE\"\n\n// https://compart.com/en/unicode/U+1F53F\n#define rt_glyph_downwards_button \"\\xF0\\x9F\\x94\\xBF\"\n\n// https://compart.com/en/unicode/U+1F5C7\n#define rt_glyph_litter_in_bin_sign \"\\xF0\\x9F\\x97\\x87\"\n\n// Checker Board\n// https://compart.com/en/unicode/U+1F67E\n#define rt_glyph_checker_board \"\\xF0\\x9F\\x9A\\xBE\"\n\n// Reverse Checker Board\n// https://compart.com/en/unicode/U+1F67F\n#define rt_glyph_reverse_checker_board \"\\xF0\\x9F\\x9A\\xBF\"\n\n// Clipboard\n// https://compart.com/en/unicode/U+1F4CB\n#define rt_glyph_clipboard \"\\xF0\\x9F\\x93\\x8B\"\n\n// Two Joined Squares https://www.compart.com/en/unicode/U+29C9\n#define rt_glyph_two_joined_squares \"\\xE2\\xA7\\x89\"\n\n// White Heavy Check Mark\n// https://compart.com/en/unicode/U+2705\n#define rt_glyph_white_heavy_check_mark \"\\xE2\\x9C\\x85\"\n\n// Negative Squared Cross Mark\n// https://compart.com/en/unicode/U+274E\n#define rt_glyph_negative_squared_cross_mark \"\\xE2\\x9D\\x8E\"\n\n// Lower Right Drop-Shadowed White Square\n// https://compart.com/en/unicode/U+274F\n#define rt_glyph_lower_right_drop_shadowed_white_square \"\\xE2\\x9D\\x8F\"\n\n// Upper Right Drop-Shadowed White Square\n// https://compart.com/en/unicode/U+2750\n#define rt_glyph_upper_right_drop_shadowed_white_square \"\\xE2\\x9D\\x90\"\n\n// Lower Right Shadowed White Square\n// https://compart.com/en/unicode/U+2751\n#define rt_glyph_lower_right_shadowed_white_square \"\\xE2\\x9D\\x91\"\n\n// Upper Right Shadowed White Square\n// https://compart.com/en/unicode/U+2752\n#define rt_glyph_upper_right_shadowed_white_square \"\\xE2\\x9D\\x92\"\n\n// Left Double Wiggly Fence\n// https://compart.com/en/unicode/U+29DA\n#define rt_glyph_left_double_wiggly_fence \"\\xE2\\xA7\\x9A\"\n\n// Right Double Wiggly Fence\n// https://compart.com/en/unicode/U+29DB\n#define rt_glyph_right_double_wiggly_fence \"\\xE2\\xA7\\x9B\"\n\n// Logical Or\n// https://compart.com/en/unicode/U+2228\n#define rt_glyph_logical_or \"\\xE2\\x88\\xA8\"\n\n// Logical And\n// https://compart.com/en/unicode/U+2227\n#define rt_glyph_logical_and \"\\xE2\\x88\\xA7\"\n\n// Double Vertical Bar (Pause)\n// https://compart.com/en/unicode/U+23F8\n#define rt_glyph_double_vertical_bar \"\\xE2\\x8F\\xB8\"\n\n// Black Square For Stop\n// https://compart.com/en/unicode/U+23F9\n#define rt_glyph_black_square_for_stop \"\\xE2\\x8F\\xB9\"\n\n// Black Circle For Record\n// https://compart.com/en/unicode/U+23FA\n#define rt_glyph_black_circle_for_record \"\\xE2\\x8F\\xBA\"\n\n// Negative Squared Latin Capital Letter \"I\"\n// https://compart.com/en/unicode/U+1F158\n#define rt_glyph_negative_squared_latin_capital_letter_i \"\\xF0\\x9F\\x85\\x98\"\n#define rt_glyph_info rt_glyph_negative_squared_latin_capital_letter_i\n\n// Circled Information Source\n// https://compart.com/en/unicode/U+1F6C8\n#define rt_glyph_circled_information_source \"\\xF0\\x9F\\x9B\\x88\"\n\n// Information Source\n// https://compart.com/en/unicode/U+2139\n#define rt_glyph_information_source \"\\xE2\\x84\\xB9\"\n\n// Squared Cool\n// https://compart.com/en/unicode/U+1F192\n#define rt_glyph_squared_cool \"\\xF0\\x9F\\x86\\x92\"\n\n// Squared OK\n// https://compart.com/en/unicode/U+1F197\n#define rt_glyph_squared_ok \"\\xF0\\x9F\\x86\\x97\"\n\n// Squared Free\n// https://compart.com/en/unicode/U+1F193\n#define rt_glyph_squared_free \"\\xF0\\x9F\\x86\\x93\"\n\n// Squared New\n// https://compart.com/en/unicode/U+1F195\n#define rt_glyph_squared_new \"\\xF0\\x9F\\x86\\x95\"\n\n// Lady Beetle\n// https://compart.com/en/unicode/U+1F41E\n#define rt_glyph_lady_beetle \"\\xF0\\x9F\\x90\\x9E\"\n\n// Brain\n// https://compart.com/en/unicode/U+1F9E0\n#define rt_glyph_brain \"\\xF0\\x9F\\xA7\\xA0\"\n\n// South West Arrow with Hook\n// https://www.compart.com/en/unicode/U+2926\n#define rt_glyph_south_west_arrow_with_hook \"\\xE2\\xA4\\xA6\"\n\n// North West Arrow with Hook\n// https://www.compart.com/en/unicode/U+2923\n#define rt_glyph_north_west_arrow_with_hook \"\\xE2\\xA4\\xA3\"\n\n// White Sun with Rays\n// https://www.compart.com/en/unicode/U+263C\n#define rt_glyph_white_sun_with_rays \"\\xE2\\x98\\xBC\"\n\n// Black Sun with Rays\n// https://www.compart.com/en/unicode/U+2600\n#define rt_glyph_black_sun_with_rays \"\\xE2\\x98\\x80\"\n\n// Sun Behind Cloud\n// https://www.compart.com/en/unicode/U+26C5\n#define rt_glyph_sun_behind_cloud \"\\xE2\\x9B\\x85\"\n\n// White Sun\n// https://www.compart.com/en/unicode/U+1F323\n#define rt_glyph_white_sun \"\\xF0\\x9F\\x8C\\xA3\"\n\n// Crescent Moon\n// https://www.compart.com/en/unicode/U+1F319\n#define rt_glyph_crescent_moon \"\\xF0\\x9F\\x8C\\x99\"\n\n// Latin Capital Letter E with Cedilla and Breve\n// https://compart.com/en/unicode/U+1E1C\n#define rt_glyph_E_with_cedilla_and_breve \"\\xE1\\xB8\\x9C\"\n\n// Box Drawings Heavy Vertical and Horizontal\n// https://compart.com/en/unicode/U+254B\n#define rt_glyph_box_drawings_heavy_vertical_and_horizontal \"\\xE2\\x95\\x8B\"\n\n// Box Drawings Light Diagonal Cross\n// https://compart.com/en/unicode/U+2573\n#define rt_glyph_box_drawings_light_diagonal_cross \"\\xE2\\x95\\xB3\"\n\n// Combining Enclosing Square\n// https://compart.com/en/unicode/U+20DE\n#define rt_glyph_combining_enclosing_square \"\\xE2\\x83\\x9E\"\n\n// Combining Enclosing Screen\n// https://compart.com/en/unicode/U+20E2\n#define rt_glyph_combining_enclosing_screen \"\\xE2\\x83\\xA2\"\n\n// Combining Enclosing Keycap\n// https://compart.com/en/unicode/U+20E3\n#define rt_glyph_combining_enclosing_keycap \"\\xE2\\x83\\xA3\"\n\n// Combining Enclosing Circle\n// https://compart.com/en/unicode/U+20DD\n#define rt_glyph_combining_enclosing_circle \"\\xE2\\x83\\x9D\"\n\n// Frame with Picture\n// https://compart.com/en/unicode/U+1F5BC\n#define rt_glyph_frame_with_picture \"\\xF0\\x9F\\x96\\xBC\"\n// with emoji variation selector: \"\\xF0\\x9F\\x96\\xBC\\xEF\\xB8\\x8F\"\n\n// Document with Picture\n// https://compart.com/en/unicode/U+1F5BB\n#define rt_glyph_document_with_picture \"\\xF0\\x9F\\x96\\xBB\"\n\n// Frame with Tiles\n// https://compart.com/en/unicode/U+1F5BD\n#define rt_glyph_frame_with_tiles \"\\xF0\\x9F\\x96\\xBD\"\n\n// Frame with an X\n// https://compart.com/en/unicode/U+1F5BE\n#define rt_glyph_frame_with_an_x \"\\xF0\\x9F\\x96\\xBE\"\n\n// Left Right Arrow\n// https://compart.com/en/unicode/U+2194\n#define rt_glyph_left_right_arrow \"\\xE2\\x86\\x94\"\n\n// Up Down Arrow\n// https://compart.com/en/unicode/U+2195\n#define rt_glyph_up_down_arrow \"\\xE2\\x86\\x95\"\n"
  },
  {
    "path": "inc/rt/rt_heap.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// It is absolutely OK to use posix compliant\n// malloc()/calloc()/realloc()/free() function calls with understanding\n// that they introduce serialization points in multi-threaded applications\n// and may be induce wait states that under pressure (all cores busy) may\n// result in prolonged wait which may not be acceptable for real time\n// processing pipelines.\n//\n// heap_if.functions may or may not be faster than malloc()/free() ...\n//\n// Some callers may find realloc() parameters more convenient to avoid\n// anti-pattern\n//      void* reallocated = realloc(p, new_size);\n//      if (reallocated != null) { p = reallocated; }\n// and avoid never ending discussion of legality and implementation\n// compliance of the situation:\n//      realloc(p /* when p == null */, ...)\n//\n// zero: true initializes allocated or reallocated tail memory to 0x00\n// be careful with zeroing heap memory. It will result in virtual\n// to physical memory mapping and may be expensive.\n\ntypedef struct rt_heap_s rt_heap_t;\n\ntypedef struct { // heap == null uses process serialized LFH\n    errno_t (*alloc)(void* *a, int64_t bytes);\n    errno_t (*alloc_zero)(void* *a, int64_t bytes);\n    errno_t (*realloc)(void* *a, int64_t bytes);\n    errno_t (*realloc_zero)(void* *a, int64_t bytes);\n    void    (*free)(void* a);\n    // heaps:\n    rt_heap_t* (*create)(bool serialized);\n    errno_t (*allocate)(rt_heap_t* heap, void* *a, int64_t bytes, bool zero);\n    // reallocate may return ERROR_OUTOFMEMORY w/o changing 'a' *)\n    errno_t (*reallocate)(rt_heap_t* heap, void* *a, int64_t bytes, bool zero);\n    void    (*deallocate)(rt_heap_t* heap, void* a);\n    int64_t (*bytes)(rt_heap_t* heap, void* a); // actual allocated size\n    void    (*dispose)(rt_heap_t* heap);\n    void    (*test)(void);\n} rt_heap_if;\n\nextern rt_heap_if rt_heap;\n\n// *) zero in reallocate applies to the newly appended bytes\n\n// On Windows rt_mem.heap is based on serialized LFH returned by GetProcessHeap()\n// https://learn.microsoft.com/en-us/windows/win32/memory/low-fragmentation-heap\n// threads can benefit from not serialized, not LFH if they allocate and free\n// memory in time critical loops.\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/rt/rt_loader.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// see:\n// https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html\n\ntypedef struct {\n    // mode:\n    int32_t const local;\n    int32_t const lazy;\n    int32_t const now;\n    int32_t const global;\n    // \"If the value of file is null, dlopen() provides a handle on a global\n    //  symbol object.\" posix\n    void* (*open)(const char* filename, int32_t mode);\n    void* (*sym)(void* handle, const char* name);\n    void  (*close)(void* handle);\n    void (*test)(void);\n} rt_loader_if;\n\nextern rt_loader_if rt_loader;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_mem.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct {\n    // whole file read only\n    errno_t (*map_ro)(const char* filename, void** data, int64_t* bytes);\n    // whole file read-write\n    errno_t (*map_rw)(const char* filename, void** data, int64_t* bytes);\n    void (*unmap)(void* data, int64_t bytes);\n    // map_resource() maps data from resources, do NOT unmap!\n    errno_t  (*map_resource)(const char* label, void** data, int64_t* bytes);\n    int32_t (*page_size)(void); // 4KB or 64KB on Windows\n    int32_t (*large_page_size)(void);  // 2MB on Windows\n    // allocate() contiguous reserved virtual address range,\n    // if possible committed to physical memory.\n    // Memory guaranteed to be aligned to page boundary.\n    // Memory is guaranteed to be initialized to zero on access.\n    void* (*allocate)(int64_t bytes_multiple_of_page_size);\n    void  (*deallocate)(void* a, int64_t bytes_multiple_of_page_size);\n    void  (*test)(void);\n} rt_mem_if;\n\nextern rt_mem_if rt_mem;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/rt/rt_nls.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct { // i18n national language support\n    void (*init)(void);\n    const char* (*locale)(void);  // \"en-US\" \"zh-CN\" etc...\n    // force locale for debugging and testing:\n    errno_t (*set_locale)(const char* locale); // only for calling thread\n    // nls(s) is same as string(strid(s), s)\n    const char* (*str)(const char* defau1t); // returns localized string\n    // strid(\"foo\") returns -1 if there is no matching\n    // ENGLISH NEUTRAL STRINGTABLE entry\n    int32_t (*strid)(const char* s);\n    // given strid > 0 returns localized string or default value\n    const char* (*string)(int32_t strid, const char* defau1t);\n} rt_nls_if;\n\nextern rt_nls_if rt_nls;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_num.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct {\n    uint64_t lo;\n    uint64_t hi;\n} rt_num128_t; // uint128_t may be supported by compiler\n\ntypedef struct {\n    rt_num128_t (*add128)(const rt_num128_t a, const rt_num128_t b);\n    rt_num128_t (*sub128)(const rt_num128_t a, const rt_num128_t b);\n    rt_num128_t (*mul64x64)(uint64_t a, uint64_t b);\n    uint64_t (*muldiv128)(uint64_t a, uint64_t b, uint64_t d);\n    uint32_t (*gcd32)(uint32_t u, uint32_t v); // greatest common denominator\n    // non-crypto strong pseudo-random number generators (thread safe)\n    uint32_t (*random32)(uint32_t *state); // \"Mulberry32\"\n    uint64_t (*random64)(uint64_t *state); // \"Trust\"\n    // \"FNV-1a\" hash functions (if bytes == 0 expects zero terminated string)\n    uint32_t (*hash32)(const char* s, int64_t bytes);\n    uint64_t (*hash64)(const char* s, int64_t bytes);\n    void     (*test)(void);\n} rt_num_if;\n\nextern rt_num_if rt_num;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/rt/rt_processes.h",
    "content": "#pragma once\n#include \"rt/rt_streams.h\"\n\nrt_begin_c\n\ntypedef struct {\n    const char* command;\n    rt_stream_if* in;\n    rt_stream_if* out;\n    rt_stream_if* err;\n    uint32_t exit_code;\n    fp64_t   timeout; // seconds\n} rt_processes_child_t;\n\n// Process name may be an the executable filename with\n// full, partial or absent pathname.\n// Case insensitive on Windows.\n\ntypedef struct {\n    const char* (*name)(void); // argv[0] like but full path\n    uint64_t  (*pid)(const char* name); // 0 if process not found\n    errno_t   (*pids)(const char* name, uint64_t* pids/*[size]*/, int32_t size,\n                      int32_t *count); // return 0, error or ERROR_MORE_DATA\n    errno_t   (*nameof)(uint64_t pid, char* name, int32_t count); // pathname\n    bool      (*present)(uint64_t pid);\n    errno_t   (*kill)(uint64_t pid, fp64_t timeout_seconds);\n    errno_t   (*kill_all)(const char* name, fp64_t timeout_seconds);\n    bool      (*is_elevated)(void); // Is process running as root/ Admin / System?\n    errno_t   (*restart_elevated)(void); // retuns error or exits on success\n    errno_t   (*run)(rt_processes_child_t* child);\n    errno_t   (*popen)(const char* command, int32_t *xc, rt_stream_if* output,\n                       fp64_t timeout_seconds); // <= 0 infinite\n    // popen() does NOT guarantee stream zero termination on errors\n    errno_t  (*spawn)(const char* command); // spawn fully detached process\n    void (*test)(void);\n} rt_processes_if;\n\nextern rt_processes_if rt_processes;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_static.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// rt_static_init(unique_name) { code_to_execute_before_main }\n\n#if defined(_MSC_VER)\n\n#if defined(_WIN64) || defined(_M_X64)\n#define _msvc_symbol_prefix_ \"\"\n#else\n#define _msvc_symbol_prefix_ \"_\"\n#endif\n\n#pragma comment(linker, \"/include:rt_force_symbol_reference\")\n\nvoid* rt_force_symbol_reference(void* symbol);\n\n#define _msvc_ctor_(sym_prefix, func)                                    \\\n  void func(void);                                                        \\\n  int32_t (* rt_array ## func)(void);                                     \\\n  int32_t func ## _wrapper(void);                                         \\\n  int32_t func ## _wrapper(void) { func();                                \\\n  rt_force_symbol_reference((void*)rt_array ## func);                     \\\n  rt_force_symbol_reference((void*)func ## _wrapper); return 0; }         \\\n  extern int32_t (* rt_array ## func)(void);                              \\\n  __pragma(comment(linker, \"/include:\" sym_prefix # func \"_wrapper\"))     \\\n  __pragma(section(\".CRT$XCU\", read))                                     \\\n  __declspec(allocate(\".CRT$XCU\"))                                        \\\n    int32_t (* rt_array ## func)(void) = func ## _wrapper;\n\n#define rt_static_init2_(func, line) _msvc_ctor_(_msvc_symbol_prefix_, \\\n    func ## _constructor_##line)                                       \\\n    void func ## _constructor_##line(void)\n\n#define rt_static_init1_(func, line) rt_static_init2_(func, line)\n\n#define rt_static_init(func) rt_static_init1_(func, __LINE__)\n\n#else\n#define rt_static_init(n) __attribute__((constructor)) \\\n        static void _init_ ## n ## __LINE__ ## _ctor(void)\n#endif\n\nvoid rt_static_init_test(void);\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_str.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct rt_str64_t {\n    char s[64];\n} rt_str64_t;\n\ntypedef struct rt_str128_t {\n    char s[128];\n} rt_str128_t;\n\ntypedef struct rt_str1024_t {\n    char s[1024];\n} rt_str1024_t;\n\ntypedef struct rt_str32K_t {\n    char s[32 * 1024];\n} rt_str32K_t;\n\n// truncating string printf:\n// char s[100]; rt_str_printf(s, \"Hello %s\", \"world\");\n// do not use with char* and char s[] parameters\n// because rt_countof(s) will be sizeof(char*) not the size of the buffer.\n\n#define rt_str_printf(s, ...) rt_str.format((s), rt_countof(s), \"\" __VA_ARGS__)\n\n#define rt_strerr(r) (rt_str.error((r)).s) // use only as rt_str_printf() parameter\n\n// The strings are expected to be UTF-8 encoded.\n// Copy functions fatal fail if the destination buffer is too small.\n// It is responsibility of the caller to make sure it won't happen.\n\ntypedef struct {\n    char* (*drop_const)(const char* s); // because of strstr() and alike\n    int32_t (*len)(const char* s);\n    int32_t (*len16)(const uint16_t* utf16);\n    int32_t (*utf8bytes)(const char* utf8, int32_t bytes); // 0 on error\n    int32_t (*glyphs)(const char* utf8, int32_t bytes); // -1 on error\n    bool (*starts)(const char* s1, const char* s2);  // s1 starts with s2\n    bool (*ends)(const char* s1, const char* s2);    // s1 ends with s2\n    bool (*istarts)(const char* s1, const char* s2); // ignore case\n    bool (*iends)(const char* s1, const char* s2);   // ignore case\n    // string truncation is fatal use strlen() to check at call site\n    void (*lower)(char* d, int32_t capacity, const char* s); // ASCII only\n    void (*upper)(char* d, int32_t capacity, const char* s); // ASCII only\n    // utf8/utf16 conversion\n    // If `chars` argument is -1, the function utf8_bytes includes the zero\n    // terminating character in the conversion and the returned byte count.\n    int32_t (*utf8_bytes)(const uint16_t* utf16, int32_t bytes); // bytes count\n    // If `bytes` argument is -1, the function utf16_chars() includes the zero\n    // terminating character in the conversion and the returned character count.\n    int32_t (*utf16_chars)(const char* utf8, int32_t bytes); // chars count\n    // utf8_bytes() and utf16_chars() return -1 on invalid UTF-8/UTF-16\n    // utf8_bytes(L\"\", -1) returns 1 for zero termination\n    // utf16_chars(\"\", -1) returns 1 for zero termination\n    // chars: -1 means both source and destination are zero terminated\n    errno_t (*utf16to8)(char* utf8, int32_t capacity,\n                        const uint16_t* utf16, int32_t chars);\n    // bytes: -1 means both source and destination are zero terminated\n    errno_t (*utf8to16)(uint16_t* utf16, int32_t capacity,\n                        const char* utf8, int32_t bytes);\n    // https://compart.com/en/unicode/U+1F41E\n    // Lady Beetle: utf16 L\"\\xD83D\\xDC1E\" utf8 \"\\xF0\\x9F\\x90\\x9E\"\n    //           surrogates:  high   low\n    bool (*utf16_is_low_surrogate)(uint16_t utf16char);\n    bool (*utf16_is_high_surrogate)(uint16_t utf16char);\n    uint32_t (*utf32)(const char* utf8, int32_t bytes); // single codepoint\n    // string formatting printf style:\n    void (*format_va)(char* utf8, int32_t count, const char* format, va_list va);\n    void (*format)(char* utf8, int32_t count, const char* format, ...);\n    // format \"dg\" digit grouped; see below for known grouping separators:\n    const char* (*grouping_separator)(void); // locale\n    // Returned const char* pointer is short-living thread local and\n    // intended to be used in the arguments list of .format() or .printf()\n    // like functions, not stored or passed for prolonged call chains.\n    // See implementation for details.\n    rt_str64_t (*int64_dg)(int64_t v, bool uint, const char* gs);\n    rt_str64_t (*int64)(int64_t v);      // with UTF-8 thin space\n    rt_str64_t (*uint64)(uint64_t v);    // with UTF-8 thin space\n    rt_str64_t (*int64_lc)(int64_t v);   // with locale separator\n    rt_str64_t (*uint64_lc)(uint64_t v); // with locale separator\n    rt_str128_t (*fp)(const char* format, fp64_t v); // respects locale\n    // errors to strings\n    rt_str1024_t (*error)(int32_t error);     // en-US\n    rt_str1024_t (*error_nls)(int32_t error); // national locale string\n    void (*test)(void);\n} rt_str_if;\n\n// Known grouping separators\n// https://en.wikipedia.org/wiki/Decimal_separator#Digit_grouping\n// coma \",\" separated decimal\n// other commonly used separators:\n// underscore \"_\" (Fortran, Kotlin)\n// apostrophe \"'\" (C++14, Rebol, Red)\n// backtick \"`\"\n// space \"\\x20\"\n// thin_space \"\\xE2\\x80\\x89\" Unicode: U+2009\n\nextern rt_str_if rt_str;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_streams.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct rt_stream_if rt_stream_if;\n\ntypedef struct rt_stream_if {\n    errno_t (*read)(rt_stream_if* s, void* data, int64_t bytes,\n                    int64_t *transferred);\n    errno_t (*write)(rt_stream_if* s, const void* data, int64_t bytes,\n                     int64_t *transferred);\n    void    (*close)(rt_stream_if* s); // optional\n} rt_stream_if;\n\ntypedef struct {\n    rt_stream_if   stream;\n    const void* data_read;\n    int64_t     bytes_read;\n    int64_t     pos_read;\n    void*       data_write;\n    int64_t     bytes_write;\n    int64_t     pos_write;\n} rt_stream_memory_if;\n\ntypedef struct {\n    void (*read_only)(rt_stream_memory_if* s,  const void* data, int64_t bytes);\n    void (*write_only)(rt_stream_memory_if* s, void* data, int64_t bytes);\n    void (*read_write)(rt_stream_memory_if* s, const void* read, int64_t read_bytes,\n                                               void* write, int64_t write_bytes);\n    void (*test)(void);\n} rt_streams_if;\n\nextern rt_streams_if rt_streams;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_threads.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct rt_event_s* rt_event_t;\n\ntypedef struct {\n    rt_event_t (*create)(void); // never returns null\n    rt_event_t (*create_manual)(void); // never returns null\n    void (*set)(rt_event_t e);\n    void (*reset)(rt_event_t e);\n    void (*wait)(rt_event_t e);\n    // returns 0 on success or -1 on timeout\n    int32_t (*wait_or_timeout)(rt_event_t e, fp64_t seconds); // seconds < 0 forever\n    // returns event index or -1 on timeout or -2 on abandon\n    int32_t (*wait_any)(int32_t n, rt_event_t events[]); // -1 on abandon\n    int32_t (*wait_any_or_timeout)(int32_t n, rt_event_t e[], fp64_t seconds);\n    void (*dispose)(rt_event_t e);\n    void (*test)(void);\n} rt_event_if;\n\nextern rt_event_if rt_event;\n\ntypedef struct rt_aligned_8 rt_mutex_s { uint8_t content[40]; } rt_mutex_t;\n\ntypedef struct {\n    void (*init)(rt_mutex_t* m);\n    void (*lock)(rt_mutex_t* m);\n    void (*unlock)(rt_mutex_t* m);\n    void (*dispose)(rt_mutex_t* m);\n    void (*test)(void);\n} rt_mutex_if;\n\nextern rt_mutex_if rt_mutex;\n\ntypedef struct thread_s* rt_thread_t;\n\ntypedef struct {\n    rt_thread_t (*start)(void (*func)(void*), void* p); // never returns null\n    errno_t     (*join)(rt_thread_t thread, fp64_t timeout_seconds); // < 0 forever\n    void        (*detach)(rt_thread_t thread); // closes handle. thread is not joinable\n    void        (*name)(const char* name); // names the thread\n    void        (*realtime)(void); // bumps calling thread priority\n    void        (*yield)(void);    // pthread_yield() / Win32: SwitchToThread()\n    void        (*sleep_for)(fp64_t seconds);\n    uint64_t    (*id_of)(rt_thread_t t);\n    uint64_t    (*id)(void); // gettid()\n    rt_thread_t (*self)(void); // Pseudo Handle may differ in access to .open(.id())\n    errno_t     (*open)(rt_thread_t* t, uint64_t id);\n    void        (*close)(rt_thread_t t);\n    void        (*test)(void);\n} rt_thread_if;\n\nextern rt_thread_if rt_thread;\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_vigil.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// better rt_assert() - augmented with printf format and parameters\n// rt_swear() - release configuration rt_assert() in honor of:\n// https://github.com/munificent/vigil\n\n#define rt_static_assertion(condition) static_assert(condition, #condition)\n\ntypedef struct {\n    int32_t (*failed_assertion)(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...);\n    int32_t (*fatal_termination)(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...);\n    int32_t (*fatal_if_error)(const char* file, int32_t line, const char* func,\n        const char* condition, errno_t r, const char* format, ...);\n    void (*test)(void);\n} rt_vigil_if;\n\nextern rt_vigil_if rt_vigil;\n\n#if defined(DEBUG)\n  #define rt_assert(b, ...) rt_suppress_constant_cond_exp           \\\n    /* const cond */                                                \\\n    (void)((!!(b)) || rt_vigil.failed_assertion(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n#else\n  #define rt_assert(b, ...) ((void)0)\n#endif\n\n// rt_swear() is runtime rt_assert() for both debug and release configurations\n\n#define rt_swear(b, ...) rt_suppress_constant_cond_exp                 \\\n    /* const cond */                                                \\\n    (void)((!!(b)) || rt_vigil.failed_assertion(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_fatal(...) (void)(rt_vigil.fatal_termination(            \\\n    __FILE__, __LINE__,  __func__, \"\",  \"\" __VA_ARGS__))\n\n#define rt_fatal_if(b, ...) rt_suppress_constant_cond_exp           \\\n    /* const cond */                                                \\\n    (void)((!(b)) || rt_vigil.fatal_termination(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_fatal_if_not(b, ...) rt_suppress_constant_cond_exp        \\\n    /* const cond */                                                 \\\n    (void)((!!(b)) || rt_vigil.fatal_termination(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_not_null(e, ...) rt_fatal_if((e) == null, \"\" __VA_ARGS__)\n\n#define rt_fatal_if_error(r, ...) rt_suppress_constant_cond_exp      \\\n    /* const cond */                                                 \\\n    (void)(rt_vigil.fatal_if_error(__FILE__, __LINE__, __func__,     \\\n                                   #r, r, \"\" __VA_ARGS__))\n\n#define rt_fatal_win32err(c, ...) rt_suppress_constant_cond_exp      \\\n    /* const cond */                                                 \\\n    (void)(rt_vigil.fatal_if_error(__FILE__, __LINE__, __func__,     \\\n                                   #c, rt_b2e(c), \"\" __VA_ARGS__))\n\nrt_end_c\n"
  },
  {
    "path": "inc/rt/rt_work.h",
    "content": "#pragma once\n#include \"rt/rt.h\"\n\nrt_begin_c\n\n// Minimalistic \"react\"-like work_queue or work items and\n// a thread based workers. See rt_worker_test() for usage.\n\ntypedef struct rt_event_s*     rt_event_t;\ntypedef struct rt_work_s       rt_work_t;\ntypedef struct rt_work_queue_s rt_work_queue_t;\n\ntypedef struct rt_work_s {\n    rt_work_queue_t* queue; // queue where the call is or was last scheduled\n    fp64_t when;       // proc() call will be made after or at this time\n    void (*work)(rt_work_t* c);\n    void*  data;       // extra data that will be passed to proc() call\n    rt_event_t  done;  // if not null signalled after calling proc() or canceling\n    rt_work_t*  next;  // next element in the queue (implementation detail)\n    bool    canceled;  // set to true inside .cancel() call\n} rt_work_t;\n\ntypedef struct rt_work_queue_s {\n    rt_work_t* head;\n    int64_t    lock; // spinlock\n    rt_event_t changed; // if not null will be signaled when head changes\n} rt_work_queue_t;\n\ntypedef struct rt_work_queue_if {\n    void (*post)(rt_work_t* c);\n    bool (*get)(rt_work_queue_t*, rt_work_t* *c);\n    void (*call)(rt_work_t* c);\n    void (*dispatch)(rt_work_queue_t* q); // all ready messages\n    void (*cancel)(rt_work_t* c);\n    void (*flush)(rt_work_queue_t* q); // cancel all requests in the queue\n} rt_work_queue_if;\n\nextern rt_work_queue_if  rt_work_queue;\n\ntypedef struct rt_worker_s {\n    rt_work_queue_t queue;\n    rt_thread_t     thread;\n    rt_event_t      wake;\n    volatile bool   quit;\n} rt_worker_t;\n\ntypedef struct rt_worker_if {\n    void    (*start)(rt_worker_t* tq);\n    void    (*post)(rt_worker_t* tq, rt_work_t* w);\n    errno_t (*join)(rt_worker_t* tq, fp64_t timeout);\n    void    (*test)(void);\n} rt_worker_if;\n\nextern rt_worker_if rt_worker;\n\n// worker thread waits for a queue's `wake` event with the timeout\n// infinity or if queue is not empty delta time till the head\n// item of the queue.\n//\n// Upon post() call the `wake` event is set and the worker thread\n// wakes up and dispatches all the items with .when less then now\n// calling function work() if it is not null and optionally signaling\n// .done event if it is not null.\n//\n// When all ready items in the queue are processed worker thread locks\n// the queue and if the head is present calculates next timeout based\n// on .when time of the head or sets timeout to infinity if the queue\n// is empty.\n//\n// Function .join() sets .quit to true signals .wake event and attempt\n// to join the worker .thread with specified timeout.\n// It is the responsibility of the caller to ensure that no other\n// work is posted after calling .join() because it will be lost.\n\nrt_end_c\n\n/*\n    Usage examples:\n\n    // The dispatch_until() is just for testing purposes.\n    // Usually rt_work_queue.dispatch(q) will be called inside each\n    // iteration of message loop of a dispatch [UI] thread.\n\n    static void dispatch_until(rt_work_queue_t* q, int32_t* i, const int32_t n);\n\n    // simple way of passing a single pointer to call_later\n\n    static void every_100ms(rt_work_t* w) {\n        int32_t* i = (int32_t*)w->data;\n        rt_println(\"i: %d\", *i);\n        (*i)++;\n        w->when = rt_clock.seconds() + 0.100;\n        rt_work_queue.post(w);\n    }\n\n    static void example_1(void) {\n        rt_work_queue_t queue = {0};\n        // if a single pointer will suffice\n        int32_t i = 0;\n        rt_work_t work = {\n            .queue = &queue,\n            .when  = rt_clock.seconds() + 0.100,\n            .work  = every_100ms,\n            .data  = &i\n        };\n        rt_work_queue.post(&work);\n        dispatch_until(&queue, &i, 4);\n    }\n\n    // extending rt_work_t with extra data:\n\n    typedef struct rt_work_ex_s {\n        union {\n            rt_work_t base;\n            struct rt_work_s;\n        };\n        struct { int32_t a; int32_t b; } s;\n        int32_t i;\n    } rt_work_ex_t;\n\n    static void every_200ms(rt_work_t* w) {\n        rt_work_ex_t* ex = (rt_work_ex_t*)w;\n        rt_println(\"ex { .i: %d, .s.a: %d .s.b: %d}\", ex->i, ex->s.a, ex->s.b);\n        ex->i++;\n        const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n        w->when = rt_clock.seconds() + 0.200;\n        rt_work_queue.post(w);\n    }\n\n    static void example_2(void) {\n        rt_work_queue_t queue = {0};\n        rt_work_ex_t work = {\n            .queue = &queue,\n            .when  = rt_clock.seconds() + 0.200,\n            .work  = every_200ms,\n            .data  = null,\n            .s = { .a = 1, .b = 2 },\n            .i = 0\n        };\n        rt_work_queue.post(&work.base);\n        dispatch_until(&queue, &work.i, 4);\n    }\n\n    static void dispatch_until(rt_work_queue_t* q, int32_t* i, const int32_t n) {\n        while (q->head != null && *i < n) {\n            rt_thread.sleep_for(0.0001); // 100 microseconds\n            rt_work_queue.dispatch(q);\n        }\n        rt_work_queue.flush(q);\n    }\n\n    // worker:\n\n    static void do_work(rt_work_t* w) {\n        // TODO: something useful\n    }\n\n    static void worker_test(void) {\n        rt_worker_t worker = { 0 };\n        rt_worker.start(&worker);\n        rt_work_t work = {\n            .when  = rt_clock.seconds() + 0.010, // 10ms\n            .done  = rt_event.create(),\n            .work  = do_work\n        };\n        rt_worker.post(&worker, &work);\n        rt_event.wait(work.done);    // await(work)\n        rt_event.dispose(work.done); // responsibility of the caller\n        rt_fatal_if_error(rt_worker.join(&worker, -1.0));\n    }\n\n    // Hint:\n    // To monitor timing turn on MSVC Output / Show Timestamp (clock button)\n\n*/\n"
  },
  {
    "path": "inc/rt/version.rc.in",
    "content": "#include \"..\\inc\\rt\\version.h\"  // rc.exe does not grok forward slashes\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION version_yy, version_mm, version_dd, version_hh\n PRODUCTVERSION version_yy, version_mm, version_dd, version_hh\n FILEFLAGSMASK 0x3fL\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x1L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904e4\"\n        BEGIN\n            VALUE \"CompanyName\", company_name\n            VALUE \"FileDescription\", file_description\n            VALUE \"FileVersion\", version_str\n            VALUE \"InternalName\", original_file_name\n            VALUE \"LegalCopyright\", copyright\n            VALUE \"OriginalFilename\", original_file_name\n            VALUE \"ProductName\", product_name\n            VALUE \"ProductVersion\", version_str\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1252\n    END\nEND\n"
  },
  {
    "path": "inc/ui/ui.h",
    "content": "#pragma once\n// alphabetical order is not possible because of headers interdependencies\n#include \"ui/rt_std.h\"\n#include \"ui/ui_core.h\"\n#include \"ui/ui_colors.h\"\n#include \"ui/ui_fuzzing.h\"\n#include \"ui/ui_gdi.h\"\n#include \"ui/ui_view.h\"\n#include \"ui/ui_containers.h\"\n#include \"ui/ui_edit_doc.h\"\n#include \"ui/ui_edit_view.h\"\n#include \"ui/ui_label.h\"\n#include \"ui/ui_button.h\"\n#include \"ui/ui_image.h\"\n#include \"ui/ui_midi.h\"\n#include \"ui/ui_slider.h\"\n#include \"ui/ui_theme.h\"\n#include \"ui/ui_toggle.h\"\n#include \"ui/ui_mbx.h\"\n#include \"ui/ui_caption.h\"\n#include \"ui/ui_app.h\"\n"
  },
  {
    "path": "inc/ui/ui_app.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// link.exe /SUBSYSTEM:WINDOWS single window application\n\ntypedef struct ui_app_message_handler_s ui_app_message_handler_t;\n\ntypedef struct ui_app_message_handler_s {\n    void* that;\n    ui_app_message_handler_t* next;\n    bool (*callback)(ui_app_message_handler_t* handler, int32_t m, \n                     int64_t wp, int64_t lp, int64_t* rt);\n} ui_app_message_handler_t;\n\ntypedef struct ui_dpi_s { // max(dpi_x, dpi_y)\n    int32_t system;  // system dpi\n    int32_t process; // process dpi\n    // 15\" diagonal monitor 3840x2160 175% scaled\n    // monitor dpi effective 168, angular 248 raw 284\n    int32_t monitor_effective; // effective with regard of user scaling\n    int32_t monitor_raw;       // with regard of physical screen size\n    int32_t monitor_angular;   // diagonal raw\n    int32_t monitor_max;       // maximum of effective,raw,angular\n    int32_t window;            // main window dpi\n} ui_dpi_t;\n\n// in inches (because monitors customary are)\n// it is not in points (1/72 inch) like font size\n// because it is awkward to express large area\n// size in typography measurements.\n\ntypedef struct ui_window_sizing_s {\n    fp32_t ini_w; // initial window width in inches\n    fp32_t ini_h; // 0,0 means set to min_w, min_h\n    fp32_t min_w; // minimum window width in inches\n    fp32_t min_h; // 0,0 means - do not care use content size\n    fp32_t max_w; // maximum window width in inches\n    fp32_t max_h; // 0,0 means as big as user wants\n    // \"sizing\" \"estimate or measure something's dimensions.\"\n\t// initial window sizing only used on the first invocation\n\t// actual user sizing is stored in the configuration and used\n\t// on all launches except the very first.\n} ui_window_sizing_t;\n\ntypedef struct ui_fms_s {\n    // when font handles are re-created on system scaling change\n    // metrics \"em\" and font geometry filled\n    ui_fm_t normal; // regular UI font ~ 11-12pt\n    ui_fm_t tiny;   // small UI font ~ 8pt\n    ui_fm_t title;  // Largest Title font\n    ui_fm_t rubric; // Subtitle font\n    ui_fm_t H1;     // bolder header font\n    ui_fm_t H2;\n    ui_fm_t H3;\n} ui_fms_t;\n\ntypedef struct { // TODO: split to ui_app_t and ui_app_if, move data after methods\n    // implemented by client:\n    const char* class_name;\n    // called before creating main window\n    void (*init)(void);\n    // called instead of init() for console apps and when .no_ui=true\n    int (*main)(void);\n    // class_name and init must be set before main()\n    void (*opened)(void);      // window has been created and shown\n    void (*every_sec)(void);   // if not null called ~ once a second\n    void (*every_100ms)(void); // called ~10 times per second\n    // .can_close() called before window is closed and can be\n    // used in a meaning of .closing()\n    bool (*can_close)(void);   // window can be closed\n    void (*closed)(void);      // window has been closed\n    void (*fini)(void);        // called before WinMain() return\n    // must be filled by application:\n    const char* title;\n    ui_window_sizing_t const window_sizing;\n    // TODO: struct {} visibility;\n    // see: ui.visibility.*\n    int32_t visibility;         // initial window_visibility state\n    int32_t last_visibility;    // last window_visibility state from last run\n    int32_t startup_visibility; // window_visibility from parent process\n    ui_canvas_t canvas;  // set by message.paint\n    // ui flags:\n    bool is_full_screen;\n    bool no_ui;      // do not create application window at all\n    bool dark_mode;  // forced dark  mode for the whole application\n    bool light_mode; // forced light mode for the whole application\n    bool no_decor;   // window w/o title bar, min/max close buttons\n    bool no_min;     // window w/o minimize button on title bar and sys menu\n    bool no_max;     // window w/o maximize button on title bar\n    bool no_size;    // window w/o maximize button on title bar\n    bool no_clip;    // allows to resize window above hosting monitor size\n    bool hide_on_minimize; // like task manager minimize means hide\n    ui_window_t window;\n    ui_icon_t icon; // may be null\n    uint64_t  tid; // main thread id\n    int32_t   exit_code; // application exit code\n    ui_dpi_t  dpi;\n    ui_rect_t wrc;  // window rectangle including non-client area\n    ui_rect_t crc;  // client rectangle\n    ui_rect_t mrc;  // monitor rectangle\n    ui_rect_t prc;  // previously invalidated paint rectangle inside crc\n    ui_rect_t work_area; // current monitor work area\n    int32_t   caption_height; // caption height\n    ui_wh_t   border;    // frame border size\n    // not to call rt_clock.seconds() too often:\n    fp64_t     now;  // ssb \"seconds since boot\" updated on each message\n    ui_view_t* root; // show_window() changes ui.hidden\n    ui_view_t* content;\n    ui_view_t* caption;\n    ui_view_t* focus; // does not affect message routing\n    struct { // font metrics and handles\n        ui_fms_t prop;  // proportional fonts\n        ui_fms_t mono;  // monospaced fonts\n    } fm;\n    // TODO: struct {} keyboard\n    // keyboard state now:\n    bool alt;\n    bool ctrl;\n    bool shift;\n    // TODO: struct {} mouse\n    // mouse buttons state\n    bool mouse_swapped;\n    bool mouse_left;   // left or if buttons are swapped - right button pressed\n    bool mouse_middle; // rarely useful\n    bool mouse_right;  // context button pressed\n    ui_point_t mouse; // mouse/touchpad pointer\n    ui_cursor_t cursor; // current cursor\n    struct {\n        ui_cursor_t arrow;\n        ui_cursor_t wait;\n        ui_cursor_t ibeam;\n        ui_cursor_t size_nwse; // north west - south east\n        ui_cursor_t size_nesw; // north east - south west\n        ui_cursor_t size_we;   // west - east\n        ui_cursor_t size_ns;   // north - south\n        ui_cursor_t size_all;  // north - south\n    } cursors;\n    struct { // animated_groot state\n        ui_view_t* view;\n        ui_view_t* focused; // focused view before animated_groot started\n        int32_t step;\n        fp64_t time; // closing time or zero\n        int32_t x; // (x,y) for tooltip (-1,y) for toast\n        int32_t y; // screen coordinates for tooltip\n    } animating;\n    ui_app_message_handler_t* handlers;\n    // post(..., delay_in_seconds, ...) can be scheduled from any thread executed\n    // on UI thread\n    void (*post)(rt_work_t* work); // work.when == 0 meaning ASAP\n    void (*request_redraw)(void);  // very fast <2 microseconds\n    void (*draw)(void); // paint window now - bad idea do not use\n    // inch to pixels and reverse translation via ui_app.dpi.window\n    fp32_t  (*px2in)(int32_t pixels);\n    int32_t (*in2px)(fp32_t inches);\n    errno_t (*set_layered_window)(ui_color_t color, float alpha);\n    bool (*is_active)(void); // is application window active\n    bool (*is_minimized)(void);\n    bool (*is_maximized)(void);\n    bool (*focused)(void); // application window has keyboard focus\n    void (*activate)(void); // request application window activation\n    void (*set_title)(const char* title);\n    void (*capture_mouse)(bool on); // capture mouse global input on/of\n    void (*move_and_resize)(const ui_rect_t* rc);\n    void (*bring_to_foreground)(void); // not necessary topmost\n    void (*make_topmost)(void);   // in foreground hierarchy of windows\n    void (*request_focus)(void);  // request application window keyboard focus\n    void (*bring_to_front)(void); // activate() + bring_to_foreground() +\n                                  // make_topmost() + request_focus()\n    // measure and layout:\n    void (*request_layout)(void); // requests layout on UI tree before paint()\n    void (*invalidate)(const ui_rect_t* rc);\n    void (*full_screen)(bool on);\n    void (*set_cursor)(ui_cursor_t c);\n    void (*close)(void); // attempts to close (can_close() permitting)\n    // forced quit() even if can_close() returns false\n    void (*quit)(int32_t ec);  // ui_app.exit_code = ec; PostQuitMessage(ec);\n    ui_timer_t (*set_timer)(uintptr_t id, int32_t milliseconds); // see notes\n    void (*kill_timer)(ui_timer_t id);\n    void (*show_window)(int32_t show); // see show_window enum\n    void (*show_toast)(ui_view_t* toast, fp64_t seconds); // toast(null) to cancel\n    void (*show_hint)(ui_view_t* tooltip, int32_t x, int32_t y, fp64_t seconds);\n    void (*toast_va)(fp64_t seconds, const char* format, va_list va);\n    void (*toast)(fp64_t seconds, const char* format, ...);\n    // caret calls must be balanced by caller\n    void (*create_caret)(int32_t w, int32_t h);\n    void (*show_caret)(void);\n    void (*move_caret)(int32_t x, int32_t y);\n    void (*hide_caret)(void);\n    void (*destroy_caret)(void);\n    // beep sounds:\n    void (*beep)(int32_t kind);\n    // registry interface:\n    void (*data_save)(const char* name, const void* data, int32_t bytes);\n    int32_t (*data_size)(const char* name);\n    int32_t (*data_load)(const char* name, void* data, int32_t bytes); // returns bytes read\n    // filename dialog:\n    // const char* filter[] =\n    //     {\"Text Files\", \".txt;.doc;.ini\",\n    //      \"Executables\", \".exe\",\n    //      \"All Files\", \"*\"};\n    // const char* fn = ui_app.open_filename(\"C:\\\\\", filter, rt_countof(filter));\n    const char* (*open_file)(const char* folder, const char* filter[], int32_t n);\n    bool (*is_stdout_redirected)(void);\n    bool (*is_console_visible)(void);\n    int  (*console_attach)(void); // attempts to attach to parent terminal\n    int  (*console_create)(void); // allocates new console\n    void (*console_show)(bool b);\n    // stats:\n    int32_t paint_count; // number of paint calls\n    fp64_t paint_time; // last paint duration in seconds\n    fp64_t paint_max;  // max of last 128 paint\n    fp64_t paint_avg;  // EMA of last 128 paints\n    fp64_t paint_fps;  // EMA of last 128 paints\n    fp64_t paint_last; // rt_clock.seconds() of last paint\n    fp64_t paint_dt_min; // minimum time between 2 paints\n} ui_app_t;\n\nextern ui_app_t ui_app;\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_button.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n#include \"ui/ui_view.h\"\n\nrt_begin_c\n\ntypedef ui_view_t ui_button_t;\n\nvoid ui_view_init_button(ui_view_t* v);\n\nvoid ui_button_init(ui_button_t* b, const char* label, fp32_t min_width_em,\n    void (*callback)(ui_button_t* b));\n\n// ui_button_clicked can only be used on static button variables\n\n#define ui_button_clicked(name, s, min_width_em, ...)       \\\n    static void name ## _clicked(ui_button_t* name) {       \\\n        (void)name; /* no warning if unused */              \\\n        { __VA_ARGS__ }                                     \\\n    }                                                       \\\n    static                                                  \\\n    ui_button_t name = {                                    \\\n        .type = ui_view_button,                             \\\n        .init = ui_view_init_button,                        \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .p.text = s,                                        \\\n        .callback = name ## _clicked,                       \\\n        .color_id = ui_color_id_button_text,                \\\n        .min_w_em = min_width_em, .min_h_em = 1.25f,        \\\n        .insets  = {                                        \\\n            .left  = ui_view_i_lr, .top    = ui_view_i_tb,  \\\n            .right = ui_view_i_lr, .bottom = ui_view_i_tb   \\\n        },                                                  \\\n        .padding = {                                        \\\n            .left  = ui_view_p_lr, .top    = ui_view_p_tb,  \\\n            .right = ui_view_p_lr, .bottom = ui_view_p_tb,  \\\n        }                                                   \\\n    }\n\n#define ui_button(s, min_width_em, clicked) {               \\\n    .type = ui_view_button,                                 \\\n    .init = ui_view_init_button,                            \\\n    .fm = &ui_app.fm.prop.normal,                           \\\n    .p.text = s,                                            \\\n    .callback = clicked,                                    \\\n    .color_id = ui_color_id_button_text,                    \\\n    .min_w_em = min_width_em, .min_h_em = 1.25f,            \\\n    .insets  = {                                            \\\n        .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n    },                                                      \\\n    .padding = {                                            \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n    }                                                       \\\n}\n\n// usage:\n//\n// ui_button_clicked(button, \"&Button\", 7.0, {\n//      if (button->state.pressed) {\n//          // do something on click that happens on release mouse button\n//      }\n// })\n//\n// or:\n//\n// static void button_flipped(ui_button_t* b) {\n//      swear(b->flip == true); // 2 state button, clicked on mouse press button\n//      if (b->state.pressed) {\n//          // show something:\n//      } else {\n//          // show something else:\n//      }\n// }\n//\n// ui_button_t button = ui_button(7.0, \"&Button\", button_flipped);\n//\n// or\n//\n// ui_button_t button = ui_view)button(button);\n// ui_view.set_text(button.text, \"&Button\");\n// button.min_w_em = 7.0;\n// button.callback = button_flipped;\n//\n// Note:\n// ui_button_clicked(button, \"&Button\", 7.0, {\n//      button->state.pressed = !button->state.pressed;\n//      // is similar to: button.flip = true but it leads thru\n//      // multiple button paint and click happens on mouse button\n//      // release not press\n// }\n\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_caption.h",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"ui/ui.h\"\n\nrt_begin_c\n\ntypedef struct ui_caption_s {\n    ui_view_t view;\n    // caption`s children:\n    ui_button_t icon;\n    ui_label_t title;\n    ui_view_t spacer;\n    ui_button_t menu; // use: ui_caption.button_menu.cb := your callback\n    ui_button_t mode; // switch between dark/light mode\n    ui_button_t mini;\n    ui_button_t maxi;\n    ui_button_t full;\n    ui_button_t quit;\n} ui_caption_t;\n\nextern ui_caption_t ui_caption;\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_colors.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef uint64_t ui_color_t; // top 2 bits determine color format\n\n/* TODO: make ui_color_t uint64_t RGBA or better yet fp32_t RGBA\n         support upto 16-16-16-14(A)bit per pixel color\n         components with 'transparent' aka 'hollow' bit\n*/\n\n#define ui_color_mask        ((ui_color_t)0xC000000000000000ULL)\n#define ui_color_undefined   ((ui_color_t)0x8000000000000000ULL)\n#define ui_color_transparent ((ui_color_t)0x4000000000000000ULL)\n#define ui_color_hdr         ((ui_color_t)0xC000000000000000ULL)\n\n#define ui_color_is_8bit(c)        ( ((c) &  ui_color_mask) == 0)\n#define ui_color_is_hdr(c)         ( ((c) &  ui_color_mask) == ui_color_hdr)\n#define ui_color_is_undefined(c)   ( ((c) &  ui_color_mask) == ui_color_undefined)\n#define ui_color_is_transparent(c) ((((c) &  ui_color_mask) == ui_color_transparent) && \\\n                                   ( ((c) & ~ui_color_mask) == 0))\n// if any other special colors or formats need to be introduced\n// (c) & ~ui_color_mask) has 2^62 possible extensions bits\n\n// ui_color_hdr A - 14 bit, R,G,B - 16 bit, all in range [0..0xFFFF]\n#define ui_color_hdr_a(c)    ((uint16_t)((((c) >> 48) & 0x3FFF) << 2))\n#define ui_color_hdr_r(c)    ((uint16_t)( ((c) >>  0) & 0xFFFF))\n#define ui_color_hdr_g(c)    ((uint16_t)( ((c) >> 16) & 0xFFFF))\n#define ui_color_hdr_b(c)    ((uint16_t)( ((c) >> 32) & 0xFFFF))\n\n#define ui_color_a(c)        ((uint8_t)(((c) >> 24) & 0xFFU))\n#define ui_color_r(c)        ((uint8_t)(((c) >>  0) & 0xFFU))\n#define ui_color_g(c)        ((uint8_t)(((c) >>  8) & 0xFFU))\n#define ui_color_b(c)        ((uint8_t)(((c) >> 16) & 0xFFU))\n\n#define ui_color_is_rgb(c)   ((uint32_t)( (c) & 0x00FFFFFFU))\n#define ui_color_is_rgba(c)  ((uint32_t)( (c) & 0xFFFFFFFFU))\n#define ui_color_is_rgbFF(c) ((uint32_t)(((c) & 0x00FFFFFFU)) | 0xFF000000U)\n\n#define ui_color_rgb(r, g, b) ((ui_color_t)(                     \\\n                              (((uint32_t)(uint8_t)(r))      ) | \\\n                              (((uint32_t)(uint8_t)(g)) <<  8) | \\\n                              (((uint32_t)(uint8_t)(b)) << 16)))\n\n\n#define ui_color_rgba(r, g, b, a)                     \\\n    ( (ui_color_t)(                                   \\\n      (ui_color_rgb(r, g, b)) |                       \\\n      ((ui_color_t)((uint32_t)((uint8_t)(a))) << 24)) \\\n    )\n\nenum {\n    ui_color_id_undefined           =  0,\n    ui_color_id_active_title        =  1,\n    ui_color_id_button_face         =  2,\n    ui_color_id_button_text         =  3,\n    ui_color_id_gray_text           =  4,\n    ui_color_id_highlight           =  5,\n    ui_color_id_highlight_text      =  6,\n    ui_color_id_hot_tracking        =  7,\n    ui_color_id_inactive_title      =  8,\n    ui_color_id_inactive_title_text =  9,\n    ui_color_id_menu_highlight      = 10,\n    ui_color_id_title_text          = 11,\n    ui_color_id_window              = 12,\n    ui_color_id_window_text         = 13,\n    ui_color_id_accent              = 14\n};\n\ntypedef struct ui_control_colors_s {\n    ui_color_t text;\n    ui_color_t background;\n    ui_color_t border;\n    ui_color_t accent; // aka highlight\n    ui_color_t gradient_top;\n    ui_color_t gradient_bottom;\n} control_colors_t;\n\ntypedef struct ui_control_state_colors_s {\n    control_colors_t disabled;\n    control_colors_t enabled;\n    control_colors_t hover;\n    control_colors_t armed;\n    control_colors_t pressed;\n} ui_control_state_colors_t;\n\ntypedef struct ui_colors_s {\n    ui_color_t (*get_color)(int32_t color_id); // ui.colors.*\n    void       (*rgb_to_hsi)(fp64_t r, fp64_t g, fp64_t b, fp64_t *h, fp64_t *s, fp64_t *i);\n    ui_color_t (*hsi_to_rgb)(fp64_t h, fp64_t s, fp64_t i,  uint8_t a);\n    // interpolate():\n    //    0.0 < multiplier < 1.0 excluding boundaries\n    //    alpha is interpolated as well\n    ui_color_t (*interpolate)(ui_color_t c0, ui_color_t c1, fp32_t multiplier);\n    ui_color_t (*gray_with_same_intensity)(ui_color_t c);\n    // multiplier ]0.0..1.0] excluding zero\n    // lighten() and darken() ignore alpha (use interpolate for alpha colors)\n    ui_color_t (*lighten)(ui_color_t rgb, fp32_t multiplier); // interpolate toward white\n    ui_color_t (*darken)(ui_color_t  rgb, fp32_t multiplier); // interpolate toward black\n    ui_color_t (*adjust_saturation)(ui_color_t c,   fp32_t multiplier);\n    ui_color_t (*multiply_brightness)(ui_color_t c, fp32_t multiplier);\n    ui_color_t (*multiply_saturation)(ui_color_t c, fp32_t multiplier);\n    ui_control_state_colors_t* controls; // colors for UI controls\n    ui_color_t const transparent;\n    ui_color_t const none; // aka CLR_INVALID in wingdi.h\n    ui_color_t const text;\n    ui_color_t const white;\n    ui_color_t const black;\n    ui_color_t const red;\n    ui_color_t const green;\n    ui_color_t const blue;\n    ui_color_t const yellow;\n    ui_color_t const cyan;\n    ui_color_t const magenta;\n    ui_color_t const gray;\n    // tone down RGB colors:\n    ui_color_t const tone_white;\n    ui_color_t const tone_red;\n    ui_color_t const tone_green;\n    ui_color_t const tone_blue;\n    ui_color_t const tone_yellow;\n    ui_color_t const tone_cyan;\n    ui_color_t const tone_magenta;\n    // miscellaneous:\n    ui_color_t const orange;\n    ui_color_t const dark_green;\n    ui_color_t const pink;\n    ui_color_t const ochre;\n    ui_color_t const gold;\n    ui_color_t const teal;\n    ui_color_t const wheat;\n    ui_color_t const tan;\n    ui_color_t const brown;\n    ui_color_t const maroon;\n    ui_color_t const barbie_pink;\n    ui_color_t const steel_pink;\n    ui_color_t const salmon_pink;\n    ui_color_t const gainsboro;\n    ui_color_t const light_gray;\n    ui_color_t const silver;\n    ui_color_t const dark_gray;\n    ui_color_t const dim_gray;\n    ui_color_t const light_slate_gray;\n    ui_color_t const slate_gray;\n    /* Named colors */\n    /* Main Panel Backgrounds */\n    ui_color_t const ennui_black; // rgb(18, 18, 18) 0x121212\n    ui_color_t const charcoal;\n    ui_color_t const onyx;\n    ui_color_t const gunmetal;\n    ui_color_t const jet_black;\n    ui_color_t const outer_space;\n    ui_color_t const eerie_black;\n    ui_color_t const oil;\n    ui_color_t const black_coral;\n    ui_color_t const obsidian;\n    /* Secondary Panels or Sidebars */\n    ui_color_t const raisin_black;\n    ui_color_t const dark_charcoal;\n    ui_color_t const dark_jungle_green;\n    ui_color_t const pine_tree;\n    ui_color_t const rich_black;\n    ui_color_t const eclipse;\n    ui_color_t const cafe_noir;\n    /* Flat Buttons */\n    ui_color_t const prussian_blue;\n    ui_color_t const midnight_green;\n    ui_color_t const charleston_green;\n    ui_color_t const rich_black_fogra;\n    ui_color_t const dark_liver;\n    ui_color_t const dark_slate_gray;\n    ui_color_t const black_olive;\n    ui_color_t const cadet;\n    /* Button highlights (hover) */\n    ui_color_t const dark_sienna;\n    ui_color_t const bistre_brown;\n    ui_color_t const dark_puce;\n    ui_color_t const wenge;\n    /* Raised button effects */\n    ui_color_t const dark_scarlet;\n    ui_color_t const burnt_umber;\n    ui_color_t const caput_mortuum;\n    ui_color_t const barn_red;\n    /* Text and Icons */\n    ui_color_t const platinum;\n    ui_color_t const anti_flash_white;\n    ui_color_t const silver_sand;\n    ui_color_t const quick_silver;\n    /* Links and Selections */\n    ui_color_t const dark_powder_blue;\n    ui_color_t const sapphire_blue;\n    ui_color_t const international_klein_blue;\n    ui_color_t const zaffre;\n    /* Additional Colors */\n    ui_color_t const fish_belly;\n    ui_color_t const rusty_red;\n    ui_color_t const falu_red;\n    ui_color_t const cordovan;\n    ui_color_t const dark_raspberry;\n    ui_color_t const deep_magenta;\n    ui_color_t const byzantium;\n    ui_color_t const amethyst;\n    ui_color_t const wisteria;\n    ui_color_t const lavender_purple;\n    ui_color_t const opera_mauve;\n    ui_color_t const mauve_taupe;\n    ui_color_t const rich_lavender;\n    ui_color_t const pansy_purple;\n    ui_color_t const violet_eggplant;\n    ui_color_t const jazzberry_jam;\n    ui_color_t const dark_orchid;\n    ui_color_t const electric_purple;\n    ui_color_t const sky_magenta;\n    ui_color_t const brilliant_rose;\n    ui_color_t const fuchsia_purple;\n    ui_color_t const french_raspberry;\n    ui_color_t const wild_watermelon;\n    ui_color_t const neon_carrot;\n    ui_color_t const burnt_orange;\n    ui_color_t const carrot_orange;\n    ui_color_t const tiger_orange;\n    ui_color_t const giant_onion;\n    ui_color_t const rust;\n    ui_color_t const copper_red;\n    ui_color_t const dark_tangerine;\n    ui_color_t const bright_marigold;\n    ui_color_t const bone;\n    /* Earthy Tones */\n    ui_color_t const sienna;\n    ui_color_t const sandy_brown;\n    ui_color_t const golden_brown;\n    ui_color_t const camel;\n    ui_color_t const burnt_sienna;\n    ui_color_t const khaki;\n    ui_color_t const dark_khaki;\n    /* Greens */\n    ui_color_t const fern_green;\n    ui_color_t const moss_green;\n    ui_color_t const myrtle_green;\n    ui_color_t const pine_green;\n    ui_color_t const jungle_green;\n    ui_color_t const sacramento_green;\n    /* Blues */\n    ui_color_t const yale_blue;\n    ui_color_t const cobalt_blue;\n    ui_color_t const persian_blue;\n    ui_color_t const royal_blue;\n    ui_color_t const iceberg;\n    ui_color_t const blue_yonder;\n    /* Miscellaneous */\n    ui_color_t const cocoa_brown;\n    ui_color_t const cinnamon_satin;\n    ui_color_t const fallow;\n    ui_color_t const cafe_au_lait;\n    ui_color_t const liver;\n    ui_color_t const shadow;\n    ui_color_t const cool_grey;\n    ui_color_t const payne_grey;\n    /* Lighter Tones for Contrast */\n    ui_color_t const timberwolf;\n    ui_color_t const silver_chalice;\n    ui_color_t const roman_silver;\n    /* Dark Mode Specific Highlights */\n    ui_color_t const electric_lavender;\n    ui_color_t const magenta_haze;\n    ui_color_t const cyber_grape;\n    ui_color_t const purple_navy;\n    ui_color_t const liberty;\n    ui_color_t const purple_mountain_majesty;\n    ui_color_t const ceil;\n    ui_color_t const moonstone_blue;\n    ui_color_t const independence;\n} ui_colors_if;\n\nextern ui_colors_if ui_colors;\n\n// TODO:\n// https://ankiewicz.com/colors/\n// https://htmlcolorcodes.com/color-names/\n// it would be super cool to implement a plethora of palettes\n// with named colors and app \"themes\" that can be switched\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_containers.h",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"ui/ui.h\"\n\nrt_begin_c\n\ntypedef struct ui_view_s ui_view_t;\n\n// Usage:\n//\n// ui_view_t* stack  = ui_view(stack);\n// ui_view_t* horizontal = ui_view(ui_view_span);\n// ui_view_t* vertical   = ui_view(ui_view_list);\n//\n// containers automatically layout child views\n// similar to SwiftUI HStack and VStack taking .align\n// .insets and .padding into account.\n//\n// Container positions every child views in the center,\n// top bottom left right edge or any of 4 corners\n// depending on .align values.\n// if child view has .max_w or .max_h set to ui.infinity == INT32_MAX\n// the views are expanded to fill the container in specified\n// direction. If child .max_w or .max_h is set to > .w or .h\n// the child view .w .h measurement are expanded accordingly.\n//\n// All containers are transparent and inset by 1/4 of an \"em\"\n// Except ui_app.root,caption,content which are also containers\n// but are not inset or padded and have default background color.\n//\n// Application implementer can override this after\n//\n// void opened(void) {\n//     ui_view.add(ui_app.view, ..., null);\n//     ui_app.view->insets = (ui_margins_t) {\n//         .left  = 0.25, .top    = 0.25,\n//         .right = 0.25, .bottom = 0.25 };\n//     ui_app.view->color = ui_colors.dark_scarlet;\n// }\n\ntypedef struct ui_view_s ui_view_t;\n\n#define ui_view(view_type) {            \\\n    .type = (ui_view_ ## view_type),    \\\n    .init = ui_view_init_ ## view_type, \\\n    .fm   = &ui_app.fm.prop.normal,     \\\n    .color = ui_color_transparent,      \\\n    .color_id = 0                       \\\n}\n\nvoid ui_view_init_stack(ui_view_t* v);\nvoid ui_view_init_span(ui_view_t* v);\nvoid ui_view_init_list(ui_view_t* v);\nvoid ui_view_init_spacer(ui_view_t* v);\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_core.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct ui_point_s { int32_t x, y; } ui_point_t;\ntypedef struct ui_rect_s { int32_t x, y, w, h; } ui_rect_t;\ntypedef struct ui_ltbr_s { int32_t left, top, right, bottom; } ui_ltrb_t;\ntypedef struct ui_wh_s   { int32_t w, h; } ui_wh_t;\n\ntypedef struct ui_window_s*  ui_window_t;\ntypedef struct ui_icon_s*    ui_icon_t;\ntypedef struct ui_canvas_s*  ui_canvas_t;\ntypedef struct ui_texture_s* ui_texture_t;\ntypedef struct ui_font_s*    ui_font_t;\ntypedef struct ui_brush_s*   ui_brush_t;\ntypedef struct ui_pen_s*     ui_pen_t;\ntypedef struct ui_cursor_s*  ui_cursor_t;\ntypedef struct ui_region_s*  ui_region_t;\n\ntypedef uintptr_t ui_timer_t; // timer not the same as \"id\" in set_timer()!\n\ntypedef struct ui_bitmap_s { // TODO: ui_ namespace\n    void* pixels;\n    int32_t w; // width\n    int32_t h; // height\n    int32_t bpp;    // \"components\" bytes per pixel\n    int32_t stride; // bytes per scanline rounded up to: (w * bpp + 3) & ~3\n    ui_texture_t texture; // device allocated texture handle\n} ui_bitmap_t;\n\n// ui_margins_t are used for padding and insets and expressed\n// in partial \"em\"s not in pixels, inches or points.\n// Pay attention that \"em\" is not square. \"M\" measurement\n// for most fonts are em.w = 0.5 * em.h\n// .em square pixel size of glyph \"m\"\n// https://en.wikipedia.org/wiki/Em_(typography)\n\ntypedef struct ui_gaps_s { // in partial \"em\"s\n    fp32_t left;\n    fp32_t top;\n    fp32_t right;\n    fp32_t bottom;\n} ui_margins_t;\n\ntypedef struct ui_s {\n    bool (*point_in_rect)(const ui_point_t* p, const ui_rect_t* r);\n    // intersect_rect(null, r0, r1) and intersect_rect(r0, r0, r1) supported.\n    bool (*intersect_rect)(ui_rect_t* destination, const ui_rect_t* r0,\n                                                   const ui_rect_t* r1);\n    ui_rect_t (*combine_rect)(const ui_rect_t* r0, const ui_rect_t* r1);\n    const int32_t infinity; // = INT32_MAX, look better\n    struct { // align bitset\n        int32_t const center; // = 0, default\n        int32_t const left;   // left|top, left|bottom, right|bottom\n        int32_t const top;\n        int32_t const right;  // right|top, right|bottom\n        int32_t const bottom;\n    } const align;\n    struct { // window visibility\n        int32_t const hide;\n        int32_t const normal;   // should be use for first .show()\n        int32_t const minimize; // activate and minimize\n        int32_t const maximize; // activate and maximize\n        int32_t const normal_na;// same as .normal but no activate\n        int32_t const show;     // shows and activates in current size and position\n        int32_t const min_next; // minimize and activate next window in Z order\n        int32_t const min_na;   // minimize but do not activate\n        int32_t const show_na;  // same as .show but no activate\n        int32_t const restore;  // from min/max to normal window size/pos\n        int32_t const defau1t;  // use Windows STARTUPINFO value\n        int32_t const force_min;// minimize even if dispatch thread not responding\n    } const visibility;\n    // TODO: remove or move inside app\n    struct { // message:\n        int32_t const animate;\n        int32_t const opening;\n        int32_t const closing;\n   } const message;\n   // TODO: remove or move inside app\n   struct { // mouse buttons bitset mask\n        struct {\n            int32_t const left;\n            int32_t const right;\n        } button;\n    } const mouse;\n    struct { // window decorations hit test results\n        int32_t const error;            // -2\n        int32_t const transparent;      // -1\n        int32_t const nowhere;          // 0\n        int32_t const client;           // 1\n        int32_t const caption;          // 2\n        int32_t const system_menu;      // 3\n        int32_t const grow_box;         // 4\n        int32_t const menu;             // 5\n        int32_t const horizontal_scroll;// 6\n        int32_t const vertical_scroll;  // 7\n        int32_t const min_button;       // 8\n        int32_t const max_button;       // 9\n        int32_t const left;             // 10\n        int32_t const right;            // 11\n        int32_t const top;              // 12\n        int32_t const top_left;         // 13\n        int32_t const top_right;        // 14\n        int32_t const bottom;           // 15\n        int32_t const bottom_left;      // 16\n        int32_t const bottom_right;     // 17\n        int32_t const border;           // 18\n        int32_t const object;           // 19\n        int32_t const close;            // 20\n        int32_t const help;             // 21\n    } const hit_test;\n    struct { // virtual keyboard keys\n        int32_t const up;\n        int32_t const down;\n        int32_t const left;\n        int32_t const right;\n        int32_t const home;\n        int32_t const end;\n        int32_t const page_up;\n        int32_t const page_down;\n        int32_t const insert;\n        int32_t const del;\n        int32_t const back;\n        int32_t const escape;\n        int32_t const enter;\n        int32_t const plus;\n        int32_t const minus;\n        int32_t const f1;\n        int32_t const f2;\n        int32_t const f3;\n        int32_t const f4;\n        int32_t const f5;\n        int32_t const f6;\n        int32_t const f7;\n        int32_t const f8;\n        int32_t const f9;\n        int32_t const f10;\n        int32_t const f11;\n        int32_t const f12;\n        int32_t const f13;\n        int32_t const f14;\n        int32_t const f15;\n        int32_t const f16;\n        int32_t const f17;\n        int32_t const f18;\n        int32_t const f19;\n        int32_t const f20;\n        int32_t const f21;\n        int32_t const f22;\n        int32_t const f23;\n        int32_t const f24;\n    } const key;\n    struct {\n        int32_t const ok;\n        int32_t const info;\n        int32_t const question;\n        int32_t const warning;\n        int32_t const error;\n    } beep;\n} ui_if;\n\nextern ui_if ui;\n\n// ui_margins_t in \"em\"s:\n//\n// The reason is that UI fonts may become larger smaller\n// for accessibility reasons with the same display\n// density in DPIs. Humanoid would expect the margins around\n// larger font text to grow with font size increase.\n// SwingUI and MacOS is using \"pt\" for padding which does\n// not account to font size changes. MacOS does weird stuff\n// with font increase - it actually decreases GPU resolution.\n// Android uses \"dp\" which is pretty much the same as scaled\n// \"pixels\" on MacOS. Windows used to use \"dialog units\" which\n// is font size based and this is where the idea is inherited from.\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/ui/ui_edit_doc.h",
    "content": "#pragma once\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nrt_begin_c\n\ntypedef struct ui_edit_str_s ui_edit_str_t;\n\ntypedef struct ui_edit_doc_s ui_edit_doc_t;\n\ntypedef struct ui_edit_notify_s ui_edit_notify_t;\n\ntypedef struct ui_edit_to_do_s ui_edit_to_do_t;\n\ntypedef struct ui_edit_pg_s { // page/glyph coordinates\n    // humans used to line:column coordinates in text\n    int32_t pn; // zero based paragraph number (\"line number\")\n    int32_t gp; // zero based glyph position (\"column\")\n} ui_edit_pg_t;\n\ntypedef union rt_begin_packed ui_edit_range_s {\n    struct { ui_edit_pg_t from; ui_edit_pg_t to; };\n    ui_edit_pg_t a[2];\n} rt_end_packed ui_edit_range_t; // \"from\"[0] \"to\"[1]\n\ntypedef struct ui_edit_text_s {\n    int32_t np;   // number of paragraphs\n    ui_edit_str_t* ps; // ps[np] paragraphs\n} ui_edit_text_t;\n\ntypedef struct ui_edit_notify_info_s {\n    bool ok; // false if ui_edit_view.replace() failed (bad utf8 or no memory)\n    const ui_edit_doc_t*   const d;\n    const ui_edit_range_t* const r; // range to be replaced\n    const ui_edit_range_t* const x; // extended range (replacement)\n    const ui_edit_text_t*  const t; // replacement text\n    // d->text.np number of paragraphs may change after replace\n    // before/after: [pnf..pnt] is inside [0..d->text.np-1]\n    int32_t const pnf; // paragraph number from\n    int32_t const pnt; // paragraph number to. (inclusive)\n    // one can safely assume that ps[pnf] was modified\n    // except empty range replace with empty text (which shouldn't be)\n    // d->text.ps[pnf..pnf + deleted] were deleted\n    // d->text.ps[pnf..pnf + inserted] were inserted\n    int32_t const deleted;  // number of deleted  paragraphs (before: 0)\n    int32_t const inserted; // paragraph inserted paragraphs (before: 0)\n} ui_edit_notify_info_t;\n\ntypedef struct ui_edit_notify_s { // called before and after replace()\n    void (*before)(ui_edit_notify_t* notify, const ui_edit_notify_info_t* ni);\n    // after() is called even if replace() failed with ok: false\n    void (*after)(ui_edit_notify_t* notify, const ui_edit_notify_info_t* ni);\n} ui_edit_notify_t;\n\ntypedef struct ui_edit_listener_s ui_edit_listener_t;\n\ntypedef struct ui_edit_listener_s {\n    ui_edit_notify_t* notify;\n    ui_edit_listener_t* prev;\n    ui_edit_listener_t* next;\n} ui_edit_listener_t;\n\ntypedef struct ui_edit_to_do_s { // undo/redo action\n    ui_edit_range_t  range;\n    ui_edit_text_t   text;\n    ui_edit_to_do_t* next; // inside undo or redo list\n} ui_edit_to_do_t;\n\ntypedef struct ui_edit_doc_s {\n    ui_edit_text_t   text;\n    ui_edit_to_do_t* undo; // undo stack\n    ui_edit_to_do_t* redo; // redo stack\n    ui_edit_listener_t* listeners;\n} ui_edit_doc_t;\n\ntypedef struct ui_edit_doc_if {\n    // init(utf8, bytes, heap:false) must have longer lifetime\n    // than document, otherwise use heap: true to copy\n    bool    (*init)(ui_edit_doc_t* d, const char* utf8_or_null,\n                    int32_t bytes, bool heap);\n    bool    (*replace)(ui_edit_doc_t* d, const ui_edit_range_t* r,\n                const char* utf8, int32_t bytes);\n    int32_t (*bytes)(const ui_edit_doc_t* d, const ui_edit_range_t* range);\n    bool    (*copy_text)(ui_edit_doc_t* d, const ui_edit_range_t* range,\n                ui_edit_text_t* text); // retrieves range into string\n    int32_t (*utf8bytes)(const ui_edit_doc_t* d, const ui_edit_range_t* range);\n    // utf8 must be at least ui_edit_doc.utf8bytes()\n    void    (*copy)(ui_edit_doc_t* d, const ui_edit_range_t* range,\n                char* utf8, int32_t bytes);\n    // undo() and push reverse into redo stack\n    bool (*undo)(ui_edit_doc_t* d); // false if there is nothing to redo\n    // redo() and push reverse into undo stack\n    bool (*redo)(ui_edit_doc_t* d); // false if there is nothing to undo\n    bool (*subscribe)(ui_edit_doc_t* d, ui_edit_notify_t* notify);\n    void (*unsubscribe)(ui_edit_doc_t* d, ui_edit_notify_t* notify);\n    void (*dispose_to_do)(ui_edit_to_do_t* to_do);\n    void (*dispose)(ui_edit_doc_t* d);\n    void (*test)(void);\n} ui_edit_doc_if;\n\nextern ui_edit_doc_if ui_edit_doc;\n\ntypedef struct ui_edit_range_if {\n    int (*compare)(const ui_edit_pg_t pg1, const ui_edit_pg_t pg2);\n    ui_edit_range_t (*order)(const ui_edit_range_t r);\n    bool            (*is_valid)(const ui_edit_range_t r);\n    bool            (*is_empty)(const ui_edit_range_t r);\n    uint64_t        (*uint64)(const ui_edit_pg_t pg); // (p << 32 | g)\n    ui_edit_pg_t    (*pg)(uint64_t ui64); // p: (ui64 >> 32) g: (int32_t)ui64\n    bool            (*inside)(const ui_edit_text_t* t,\n                              const ui_edit_range_t r);\n    ui_edit_range_t (*intersect)(const ui_edit_range_t r1,\n                                 const ui_edit_range_t r2);\n    const ui_edit_range_t* const invalid_range; // {{-1,-1},{-1,-1}}\n} ui_edit_range_if;\n\nextern ui_edit_range_if ui_edit_range;\n\ntypedef struct ui_edit_text_if {\n    bool    (*init)(ui_edit_text_t* t, const char* utf, int32_t b, bool heap);\n\n    int32_t (*bytes)(const ui_edit_text_t* t, const ui_edit_range_t* r);\n    // end() last paragraph, last glyph in text\n    ui_edit_pg_t    (*end)(const ui_edit_text_t* t);\n    ui_edit_range_t (*end_range)(const ui_edit_text_t* t);\n    ui_edit_range_t (*all_on_null)(const ui_edit_text_t* t,\n                                   const ui_edit_range_t* r);\n    ui_edit_range_t (*ordered)(const ui_edit_text_t* t,\n                               const ui_edit_range_t* r);\n    bool    (*dup)(ui_edit_text_t* t, const ui_edit_text_t* s);\n    bool    (*equal)(const ui_edit_text_t* t1, const ui_edit_text_t* t2);\n    bool    (*copy_text)(const ui_edit_text_t* t, const ui_edit_range_t* range,\n                ui_edit_text_t* to);\n    void    (*copy)(const ui_edit_text_t* t, const ui_edit_range_t* range,\n                char* to, int32_t bytes);\n    bool    (*replace)(ui_edit_text_t* t, const ui_edit_range_t* r,\n                const ui_edit_text_t* text, ui_edit_to_do_t* undo_or_null);\n    bool    (*replace_utf8)(ui_edit_text_t* t, const ui_edit_range_t* r,\n                const char* utf8, int32_t bytes, ui_edit_to_do_t* undo_or_null);\n    void    (*dispose)(ui_edit_text_t* t);\n} ui_edit_text_if;\n\nextern ui_edit_text_if ui_edit_text;\n\ntypedef struct rt_begin_packed ui_edit_str_s {\n    char* u;    // always correct utf8 bytes not zero terminated(!) sequence\n    // s.g2b[s.g + 1] glyph to byte position inside s.u[]\n    // s.g2b[0] == 0, s.g2b[s.glyphs] == s.bytes\n    int32_t* g2b;  // g2b_0 or heap allocated glyphs to bytes indices\n    int32_t  b;    // number of bytes\n    int32_t  c;    // when capacity is zero .u is not heap allocated\n    int32_t  g;    // number of glyphs\n} rt_end_packed ui_edit_str_t;\n\ntypedef struct ui_edit_str_if {\n    bool (*init)(ui_edit_str_t* s, const char* utf8, int32_t bytes, bool heap);\n    void (*swap)(ui_edit_str_t* s1, ui_edit_str_t* s2);\n    int32_t (*gp_to_bp)(const char* s, int32_t bytes, int32_t gp); // or -1\n    int32_t (*bytes)(ui_edit_str_t* s, int32_t from, int32_t to); // glyphs\n    bool (*expand)(ui_edit_str_t* s, int32_t capacity); // reallocate\n    void (*shrink)(ui_edit_str_t* s); // get rid of extra heap memory\n    bool (*replace)(ui_edit_str_t* s, int32_t from, int32_t to, // glyphs\n                    const char* utf8, int32_t bytes); // [from..to[ exclusive\n    bool (*is_zwj)(uint32_t utf32); // zero width joiner\n    bool (*is_letter)(uint32_t utf32); // in European Alphabets\n    bool (*is_digit)(uint32_t utf32);\n    bool (*is_symbol)(uint32_t utf32);\n    bool (*is_alphanumeric)(uint32_t utf32);\n    bool (*is_blank)(uint32_t utf32); // white space\n    bool (*is_punctuation)(uint32_t utf32);\n    bool (*is_combining)(uint32_t utf32);\n    bool (*is_spacing)(uint32_t utf32); // spacing modifiers\n    bool (*is_cjk_or_emoji)(uint32_t utf32);\n    bool (*can_break)(uint32_t cp1, uint32_t cp2);\n    void (*test)(void);\n    void (*free)(ui_edit_str_t* s);\n    const ui_edit_str_t* const empty;\n} ui_edit_str_if;\n\nextern ui_edit_str_if ui_edit_str;\n\n/*\n    For caller convenience the bytes parameter in all calls can be set\n    to -1 for zero terminated utf8 strings which results in treating\n    strlen(utf8) as number of bytes.\n\n    ui_edit_str.init()\n            initializes not zero terminated utf8 string that may be\n            allocated on the heap or point out to an outside memory\n            location that should have longer lifetime and will be\n            treated as read only. init() may return false if\n            heap.alloc() returns null or the utf8 bytes sequence\n            is invalid.\n            s.b is number of bytes in the initialized string;\n            s.c is set to heap allocated capacity is set to zero\n            for strings that are not allocated on the heap;\n            s.g is number of the utf8 glyphs (aka Unicode codepoints)\n            in the string;\n            s.g2b[] is an array of s.g + 1 integers that maps glyph\n            positions to byte positions in the utf8 string. The last\n            element is number of bytes in the s.u memory.\n            Called must zero out the string struct before calling init().\n\n    ui_edit_str.bytes()\n            returns number of bytes in utf8 string in the exclusive\n            range [from..to[ between string glyphs.\n\n    ui_edit_str.replace()\n            replaces utf8 string in the exclusive range [from..to[\n            with the new utf8 string. The new string may be longer\n            or shorter than the replaced string. The function returns\n            false if the new string is invalid utf8 sequence or\n            heap allocation fails. The called must ensure that the\n            range [from..to[ is valid, failure to do so is a fatal\n            error. ui_edit_str.replace() moves string content to the heap.\n\n    ui_edit_str.free()\n            deallocates all heap allocated memory and zero out string\n            struct. It is incorrect to call free() on the string that\n            was not initialized or already freed.\n\n    All ui_edit_str_t keep \"precise\" number of utf8 bytes.\n    Caller may allocate extra byte and set it to 0x00\n    after retrieving and copying data from ui_edit_str if\n    the string content is intended to be used by any\n    other API that expects zero terminated strings.\n*/\n\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_edit_view.h",
    "content": "#pragma once\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nrt_begin_c\n\n// important ui_edit_view_t will refuse to layout into a box smaller than\n// width 3 x fm->em.w height 1 x fm->em.h\n\ntypedef struct ui_edit_view_s ui_edit_view_t;\n\ntypedef struct ui_edit_str_s ui_edit_str_t;\n\ntypedef struct ui_edit_doc_s ui_edit_doc_t;\n\ntypedef struct ui_edit_notify_s ui_edit_notify_t;\n\ntypedef struct ui_edit_to_do_s ui_edit_to_do_t;\n\ntypedef struct ui_edit_pr_s { // page/run coordinates\n    int32_t pn; // paragraph number\n    int32_t rn; // run number inside paragraph\n} ui_edit_pr_t;\n\ntypedef struct ui_edit_run_s {\n    int32_t bp;     // position in bytes  since start of the paragraph\n    int32_t gp;     // position in glyphs since start of the paragraph\n    int32_t bytes;  // number of bytes in this `run`\n    int32_t glyphs; // number of glyphs in this `run`\n    int32_t pixels; // width in pixels\n} ui_edit_run_t;\n\n// ui_edit_paragraph_t.initially text will point to readonly memory\n// with .allocated == 0; as text is modified it is copied to\n// heap and reallocated there.\n\ntypedef struct ui_edit_paragraph_s { // \"paragraph\" view consists of wrapped runs\n    int32_t runs;       // number of runs in this paragraph\n    ui_edit_run_t* run; // heap allocated array[runs]\n} ui_edit_paragraph_t;\n\ntypedef struct ui_edit_notify_view_s {\n    ui_edit_notify_t notify;\n    void*            that; // specific for listener\n    uintptr_t        data; // before -> after listener data\n} ui_edit_notify_view_t;\n\ntypedef struct ui_edit_view_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_edit_doc_t* doc; // document\n    ui_edit_notify_view_t listener;\n    ui_edit_range_t selection; // \"from\" selection[0] \"to\" selection[1]\n    ui_point_t caret; // (-1, -1) off\n    int32_t caret_width; // in pixels\n    ui_edit_pr_t scroll; // left top corner paragraph/run coordinates\n    int32_t last_x;    // last_x for up/down caret movement\n    ui_ltrb_t inside;  // inside insets space\n    struct {\n        int32_t w;       // inside.right - inside.left\n        int32_t h;       // inside.bottom - inside.top\n        int32_t buttons; // bit 0 and bit 1 for LEFT and RIGHT mouse buttons down\n    } edit;\n    // number of fully (not partially clipped) visible `runs' from top to bottom:\n    int32_t visible_runs;\n    // TODO: remove focused because it is the same as caret != (-1, -1)\n    bool focused;     // is focused and created caret\n    bool ro;          // Read Only\n    bool sle;         // Single Line Edit\n    bool hide_word_wrap; // do not paint word wrap\n    int32_t shown;    // debug: caret show/hide counter 0|1\n    // paragraphs memory:\n    ui_edit_paragraph_t* para; // para[e->doc->text.np]\n} ui_edit_view_t;\n\ntypedef struct ui_edit_view_if {\n    void (*init)(ui_edit_view_t* e, ui_edit_doc_t* d);\n    void (*set_font)(ui_edit_view_t* e, ui_fm_t* fm); // see notes below (*)\n    void (*move)(ui_edit_view_t* e, ui_edit_pg_t pg); // move caret clear selection\n    // replace selected text. If bytes < 0 text is treated as zero terminated\n    void (*replace)(ui_edit_view_t* e, const char* text, int32_t bytes);\n    // call save(e, null, &bytes) to retrieve number of utf8\n    // bytes required to save whole text including 0x00 terminating bytes\n    errno_t (*save)(ui_edit_view_t* e, char* text, int32_t* bytes);\n    void (*copy)(ui_edit_view_t* e);  // to clipboard\n    void (*cut)(ui_edit_view_t* e);   // to clipboard\n    // replace selected text with content of clipboard:\n    void (*paste)(ui_edit_view_t* e); // from clipboard\n    void (*select_all)(ui_edit_view_t* e); // select whole text\n    void (*erase)(ui_edit_view_t* e); // delete selected text\n    // keyboard actions dispatcher:\n    void (*key_down)(ui_edit_view_t* e);\n    void (*key_up)(ui_edit_view_t* e);\n    void (*key_left)(ui_edit_view_t* e);\n    void (*key_right)(ui_edit_view_t* e);\n    void (*key_page_up)(ui_edit_view_t* e);\n    void (*key_page_down)(ui_edit_view_t* e);\n    void (*key_home)(ui_edit_view_t* e);\n    void (*key_end)(ui_edit_view_t* e);\n    void (*key_delete)(ui_edit_view_t* e);\n    void (*key_backspace)(ui_edit_view_t* e);\n    void (*key_enter)(ui_edit_view_t* e);\n    // called when ENTER keyboard key is pressed in single line mode\n    void (*enter)(ui_edit_view_t* e);\n    // fuzzer test:\n    void (*fuzz)(ui_edit_view_t* e);      // start/stop fuzzing test\n    void (*dispose)(ui_edit_view_t* e);\n} ui_edit_view_if;\n\nextern ui_edit_view_if ui_edit_view;\n\n/*\n    Notes:\n    set_font()\n        neither edit.view.font = font nor measure()/layout() functions\n        do NOT dispose paragraphs layout unless geometry changed because\n        it is quite expensive operation. But choosing different font\n        on the fly needs to re-layout all paragraphs. Thus caller needs\n        to set font via this function instead which also requests\n        edit UI element re-layout.\n\n    .ro\n        readonly edit->ro is used to control readonly mode.\n        If edit control is readonly its appearance does not change but it\n        refuses to accept any changes to the rendered text.\n\n    .wb\n        wordbreak this attribute was removed as poor UX human experience\n        along with single line scroll editing. See note below about .sle.\n\n    .sle\n        single line edit control.\n        Edit UI element does NOT support horizontal scroll and breaking\n        words semantics as it is poor UX human experience. This is not\n        how humans (apart of software developers) edit text.\n        If content of the edit UI element is wider than the bounding box\n        width the content is broken on word boundaries and vertical scrolling\n        semantics is supported. Layouts containing edit control of the single\n        line height are strongly encouraged to enlarge edit control layout\n        vertically on as needed basis similar to Google Search Box behavior\n        change implemented in 2023.\n        If multiline is set to true by the callers code the edit UI layout\n        snaps text to the top of x,y,w,h box otherwise the vertical space\n        is distributed evenly between single line of text and top bottom\n        margins.\n        IMPORTANT: SLE resizes itself vertically to accommodate for\n        input that is too wide. If caller wants to limit vertical space it\n        will need to hook .measure() function of SLE and do the math there.\n*/\n\n/*\n    For caller convenience the bytes parameter in all calls can be set\n    to -1 for zero terminated utf8 strings which results in treating\n    strlen(utf8) as number of bytes.\n\n    ui_edit_str.init()\n            initializes not zero terminated utf8 string that may be\n            allocated on the heap or point out to an outside memory\n            location that should have longer lifetime and will be\n            treated as read only. init() may return false if\n            heap.alloc() returns null or the utf8 bytes sequence\n            is invalid.\n            s.b is number of bytes in the initialized string;\n            s.c is set to heap allocated capacity is set to zero\n            for strings that are not allocated on the heap;\n            s.g is number of the utf8 glyphs (aka Unicode codepoints)\n            in the string;\n            s.g2b[] is an array of s.g + 1 integers that maps glyph\n            positions to byte positions in the utf8 string. The last\n            element is number of bytes in the s.u memory.\n            Called must zero out the string struct before calling init().\n\n    ui_edit_str.bytes()\n            returns number of bytes in utf8 string in the exclusive\n            range [from..to[ between string glyphs.\n\n    ui_edit_str.replace()\n            replaces utf8 string in the exclusive range [from..to[\n            with the new utf8 string. The new string may be longer\n            or shorter than the replaced string. The function returns\n            false if the new string is invalid utf8 sequence or\n            heap allocation fails. The called must ensure that the\n            range [from..to[ is valid, failure to do so is a fatal\n            error. ui_edit_str.replace() moves string content to the heap.\n\n    ui_edit_str.free()\n            deallocates all heap allocated memory and zero out string\n            struct. It is incorrect to call free() on the string that\n            was not initialized or already freed.\n\n    All ui_edit_str_t keep \"precise\" number of utf8 bytes.\n    Caller may allocate extra byte and set it to 0x00\n    after retrieving and copying data from ui_edit_str if\n    the string content is intended to be used by any\n    other API that expects zero terminated strings.\n*/\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_fuzzing.h",
    "content": "#pragma once\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nrt_begin_c\n\n// https://en.wikipedia.org/wiki/Fuzzing\n// aka \"Monkey\" testing\n\ntypedef struct ui_fuzzing_s {\n    rt_work_t    base;\n    const char*  utf8; // .character(utf8)\n    int32_t      key;  // .key_pressed(key)/.key_released(key)\n    ui_point_t*  pt;   // .move_move()\n    // key_press and character\n    bool         alt;\n    bool         ctrl;\n    bool         shift;\n    // mouse modifiers\n    bool         left; // tap() buttons:\n    bool         right;\n    bool         double_tap;\n    bool         long_press;\n    // custom\n    int32_t      op;\n    void*        data;\n} ui_fuzzing_t;\n\ntypedef struct ui_fuzzing_if {\n    void (*start)(uint32_t seed);\n    bool (*is_running)(void);\n    bool (*from_inside)(void); // true if called originated inside fuzzing\n    void (*next_random)(ui_fuzzing_t* f); // called if `next` is null\n    void (*dispatch)(ui_fuzzing_t* f);    // dispatch work\n    // next() called instead of random if not null\n    void (*next)(ui_fuzzing_t* f);\n    // custom() called instead of dispatch() if not null\n    void (*custom)(ui_fuzzing_t* f);\n    void (*stop)(void);\n} ui_fuzzing_if;\n\nextern ui_fuzzing_if ui_fuzzing;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/ui/ui_gdi.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\n// Graphic Device Interface (selected parts of Windows GDI)\n\nenum {  // TODO: into gdi int32_t const\n    ui_gdi_font_quality_default = 0,\n    ui_gdi_font_quality_draft = 1,\n    ui_gdi_font_quality_proof = 2, // anti-aliased w/o ClearType rainbows\n    ui_gdi_font_quality_nonantialiased = 3,\n    ui_gdi_font_quality_antialiased = 4,\n    ui_gdi_font_quality_cleartype = 5,\n    ui_gdi_font_quality_cleartype_natural = 6\n};\n\ntypedef struct ui_fm_s { // font metrics\n    ui_font_t font;\n    ui_wh_t em;        // \"em\" square point size expressed in pixels *)\n    // https://learn.microsoft.com/en-us/windows/win32/gdi/string-widths-and-heights\n    int32_t height;    // font height in pixels\n    int32_t baseline;  // bottom of the glyphs sans descenders (align of multi-font text)\n    int32_t ascent;    // the maximum glyphs extend above the baseline\n    int32_t descent;   // maximum height of descenders\n    int32_t x_height;  // small letters height\n    int32_t cap_em_height;    // Capital letter \"M\" height\n    int32_t internal_leading; // accents and diacritical marks goes there\n    int32_t external_leading;\n    int32_t average_char_width;\n    int32_t max_char_width;\n    int32_t line_gap;  // gap between lines of text\n    ui_wh_t subscript; // height\n    ui_point_t subscript_offset;\n    ui_wh_t superscript;    // height\n    ui_point_t superscript_offset;\n    int32_t underscore;     // height\n    int32_t underscore_position;\n    int32_t strike_through; // height\n    int32_t strike_through_position;\n    int32_t design_units_per_em; // aka EM square ~ 2048\n    ui_rect_t box; // bounding box of the glyphs in design units\n    bool mono;\n} ui_fm_t;\n\n/* see: https://github.com/leok7v/ui/wiki/Typography-Line-Terms\n   https://en.wikipedia.org/wiki/Typeface#Font_metrics\n\n   Example em55x55 H1 font @ 192dpi:\n    _   _                   _              ___    <- y:0\n   (_)_(_)                 | |             ___ /\\    \"diacritics circumflex\"\n     / \\   __ _ _   _ _ __ | |_ ___ _ __       ||\n    / _ \\ / _` | | | | '_ \\| __/ _ \\ '_ \\      ||    .ascend:30\n   / ___ \\ (_| | |_| | |_) | ||  __/ | | |     ||     max extend above baseline\n  /_/   \\_\\__, |\\__, | .__/ \\__\\___|_| |_| ___ || <- .baseline:44\n           __/ | __/ | |                       ||    .descend:11\n          |___/ |___/|_|                   ___ \\/     max height of descenders\n                                                  <- .height:55\n  em: 55x55\n  ascender for \"diacritics circumflex\" is (h:55 - a:30 - d:11) = 14\n*/\n\ntypedef struct ui_gdi_ta_s { // text attributes\n    const ui_fm_t* fm; // font metrics\n    int32_t color_id;  // <= 0 use color\n    ui_color_t color;  // ui_colors.undefined() use color_id\n    bool measure;      // measure only do not draw\n} ui_gdi_ta_t;\n\ntypedef struct {\n    struct {\n        struct {\n            ui_gdi_ta_t const normal;\n            ui_gdi_ta_t const title;\n            ui_gdi_ta_t const rubric;\n            ui_gdi_ta_t const H1;\n            ui_gdi_ta_t const H2;\n            ui_gdi_ta_t const H3;\n        } prop;\n        struct {\n            ui_gdi_ta_t const normal;\n            ui_gdi_ta_t const title;\n            ui_gdi_ta_t const rubric;\n            ui_gdi_ta_t const H1;\n            ui_gdi_ta_t const H2;\n            ui_gdi_ta_t const H3;\n        } mono;\n    } const ta;\n    void (*init)(void);\n    void (*fini)(void);\n    void (*begin)(ui_bitmap_t* bitmap_or_null);\n    // all paint must be done in between\n    void (*end)(void);\n    // TODO: move to ui_colors\n    uint32_t (*color_rgb)(ui_color_t c); // rgb color\n    // bpp bytes (not bits!) per pixel. bpp = -3 or -4 does not swap RGB to BRG:\n    void (*bitmap_init)(ui_bitmap_t* bitmap, int32_t w, int32_t h, int32_t bpp,\n        const uint8_t* pixels);\n    void (*bitmap_init_rgbx)(ui_bitmap_t* bitmap, int32_t w, int32_t h,\n        int32_t bpp, const uint8_t* pixels); // sets all alphas to 0xFF\n    void (*bitmap_dispose)(ui_bitmap_t* bitmap);\n    void (*set_clip)(int32_t x, int32_t y, int32_t w, int32_t h);\n    // use set_clip(0, 0, 0, 0) to clear clip region\n    void (*pixel)(int32_t x, int32_t y, ui_color_t c);\n    void (*line)(int32_t x0, int32_t y1, int32_t x2, int32_t y2,\n                      ui_color_t c);\n    void (*frame)(int32_t x, int32_t y, int32_t w, int32_t h,\n                      ui_color_t c);\n    void (*rect)(int32_t x, int32_t y, int32_t w, int32_t h,\n                      ui_color_t border, ui_color_t fill);\n    void (*fill)(int32_t x, int32_t y, int32_t w, int32_t h, ui_color_t c);\n    void (*poly)(ui_point_t* points, int32_t count, ui_color_t c);\n    void (*circle)(int32_t center_x, int32_t center_y, int32_t odd_radius,\n        ui_color_t border, ui_color_t fill);\n    void (*rounded)(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t odd_radius, ui_color_t border, ui_color_t fill);\n    void (*gradient)(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t rgba_from, ui_color_t rgba_to, bool vertical);\n    // dx, dy, dw, dh destination rectangle\n    // ix, iy, iw, ih rectangle inside pixels[height][width]\n    void (*pixels)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        int32_t bpp, const uint8_t* pixels); // bytes per pixel\n    void (*greyscale)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    void (*bgr)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    void (*bgrx)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    // alpha() blend only works with device allocated bitmaps\n    void (*alpha)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* bitmap, fp64_t alpha); // alpha blend\n    // bitmap() only works with device allocated bitmaps\n    void (*bitmap)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* bitmap);\n    void (*icon)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        ui_icon_t icon);\n    // text:\n    void (*cleartype)(bool on); // system wide change: don't use\n    void (*font_smoothing_contrast)(int32_t c); // [1000..2202] or -1 for 1400 default\n    ui_font_t (*create_font)(const char* family, int32_t height, int32_t quality);\n    // custom font, quality: -1 \"as is\"\n    ui_font_t (*font)(ui_font_t f, int32_t height, int32_t quality);\n    void      (*delete_font)(ui_font_t f);\n    void (*dump_fm)(ui_font_t f); // dump font metrics\n    void (*update_fm)(ui_fm_t* fm, ui_font_t f); // fills font metrics\n    ui_wh_t (*text_va)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        const char* format, va_list va);\n    ui_wh_t (*text)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        const char* format, ...);\n    ui_wh_t (*multiline_va)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        int32_t w, const char* format, va_list va); // \"w\" can be zero\n    ui_wh_t (*multiline)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        int32_t w, const char* format, ...);\n    // x[rt_str.glyphs(utf8, bytes)] = {x0, x1, x2, ...}\n    ui_wh_t (*glyphs_placement)(const ui_gdi_ta_t* ta, const char* utf8,\n        int32_t bytes, int32_t x[/*glyphs + 1*/], int32_t glyphs);\n} ui_gdi_if;\n\nextern ui_gdi_if ui_gdi;\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_image.h",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nrt_begin_c\n\n// \"image view\"\n\n// To enable zoom/pan make view focusable:\n// iv.focusable = true;\n\n// Field .image may have .pixels pointer and .bitmap == null.\n// If this is the case the direct pixels transfer to the\n// device is used. RGBA bitmaps must be allocated on the\n// device otherwise ui_gdi.rgbx() call is used and alpha\n// is ignored.\n\ntypedef struct ui_image_s ui_image_t;\n\ntypedef struct ui_image_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_bitmap_t image; // view does NOT own or dispose image->bitmap\n    fp64_t     alpha; // for rgba images\n    // actual scale() is: z = 2 ^ (zn - 1) / 2 ^ (zd - 1)\n    int32_t zoom; // 0..8\n    // 0=16:1 1=8:1 2=4:1 3=2:1 4=1:1 5=1:2 6=1:4 7=1:8 8=1:16\n    int32_t zn; // zoom nominator (1, 2, 3, ...)\n    int32_t zd; // zoom denominator (1, 2, 3, ...)\n    fp64_t  sx; // shift x [0..1.0] in view coordinates\n    fp64_t  sy; // shift y [0..1.0]\n    struct { // only visible when focused\n        ui_view_t   bar; // ui_view(span) {zoom in, zoom 1:1, zoom out, help}\n        ui_button_t copy; // copy image to clipboard\n        ui_button_t zoom_in;\n        ui_button_t zoom_1t1; // 1:1\n        ui_button_t zoom_out;\n        ui_button_t fit;\n        ui_button_t fill;\n        ui_button_t help;\n        ui_label_t  ratio;\n    } tool;\n    ui_point_t drag_start;\n    fp64_t when; // to hide toolbar\n    bool fit;    // best fit into view\n    bool fill;   // fill entire view\n    // fit and fill cannot be true at the same time\n    // when fit: false and fill: false the zoom ratio is in effect\n} ui_image_t;\n\ntypedef struct ui_image_if {\n    void      (*init)(ui_image_t* iv);\n    void      (*init_with)(ui_image_t* iv, const uint8_t* pixels,\n                           int32_t width, int32_t height,\n                           int32_t bpp, int32_t stride);\n    // ration can only be: 16:1 8:1 4:1 2:1 1:1 1:2 1:4 1:8 1:16\n    // but ignored if .fit or .fill is true\n    void      (*ratio)(ui_image_t* iv, int32_t nominator, int32_t denominator);\n    fp64_t    (*scale)(ui_image_t* iv); // 2 ^ (zn - 1) / 2 ^ (zd - 1)\n    ui_rect_t (*position)(ui_image_t* iv);\n} ui_image_if;\n\nextern ui_image_if ui_image;\n\nrt_end_c\n\n"
  },
  {
    "path": "inc/ui/ui_label.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n#include \"ui/ui_view.h\"\n\nrt_begin_c\n\ntypedef ui_view_t ui_label_t;\n\nvoid ui_view_init_label(ui_view_t* v);\n\n// label insets and padding left/right are intentionally\n// smaller than button/slider/toggle controls\n\n#define ui_label(min_width_em, s) {                    \\\n    .type = ui_view_label, .init = ui_view_init_label, \\\n    .fm = &ui_app.fm.prop.normal,                      \\\n    .p.text = s,                                       \\\n    .min_w_em = min_width_em, .min_h_em = 1.25f,       \\\n    .insets  = {                                       \\\n        .left  = ui_view_i_lr, .top    = ui_view_i_tb, \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb  \\\n    },                                                 \\\n    .padding = {                                       \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb, \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb, \\\n    }                                                  \\\n}\n\n// text with \"&\" keyboard shortcuts:\n\nvoid ui_label_init(ui_label_t* t, fp32_t min_w_em, const char* format, ...);\nvoid ui_label_init_va(ui_label_t* t, fp32_t min_w_em, const char* format, va_list va);\n\n// use this macro for initialization:\n//    ui_label_t label = ui_label(min_width_em, s);\n// or:\n//    label = (ui_label_t)ui_label(min_width_em, s);\n// which is subtle C difference of constant and\n// variable initialization and I did not find universal way\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_mbx.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n#include \"ui/ui_view.h\"\n\nrt_begin_c\n\n// Options like:\n//   \"Yes\"|\"No\"|\"Abort\"|\"Retry\"|\"Ignore\"|\"Cancel\"|\"Try\"|\"Continue\"\n// maximum number of choices presentable to human is 4.\n\ntypedef struct {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_label_t   label;\n    ui_button_t  button[4];\n    int32_t      option; // -1 or option chosen by user\n    const char** options;\n} ui_mbx_t;\n\nvoid ui_view_init_mbx(ui_view_t* v);\n\nvoid ui_mbx_init(ui_mbx_t* mx, const char* option[], const char* format, ...);\n\n// ui_mbx_on_choice can only be used on static mbx variables\n\n\n#define ui_mbx_chosen(name, s, code, ...)                        \\\n                                                                 \\\n    static char* name ## _options[] = { __VA_ARGS__, null };     \\\n                                                                 \\\n    static void name ## _chosen(ui_mbx_t* m, int32_t option) {   \\\n        (void)m; (void)option; /* no warnings if unused */       \\\n        code                                                     \\\n    }                                                            \\\n    static                                                       \\\n    ui_mbx_t name = {                                            \\\n        .view = {                                                \\\n            .type = ui_view_mbx,                                 \\\n            .init = ui_view_init_mbx,                            \\\n            .fm = &ui_app.fm.prop.normal,                        \\\n            .p.text = s,                                         \\\n            .callback = name ## _chosen,                         \\\n            .padding = { .left  = 0.125, .top    = 0.25,         \\\n                         .right = 0.125, .bottom = 0.25 },       \\\n            .insets  = { .left  = 0.125, .top    = 0.25,         \\\n                         .right = 0.125, .bottom = 0.25 }        \\\n        },                                                       \\\n        .options = name ## _options                              \\\n    }\n\n#define ui_mbx(s, chosen, ...) {                            \\\n    .view = {                                               \\\n        .type = ui_view_mbx, .init = ui_view_init_mbx,      \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .p.text = s,                                        \\\n        .callback = chosen,                                 \\\n        .padding = { .left  = 0.125, .top    = 0.25,        \\\n                     .right = 0.125, .bottom = 0.25 },      \\\n        .insets  = { .left  = 0.125, .top    = 0.25,        \\\n                     .right = 0.125, .bottom = 0.25 }       \\\n    },                                                      \\\n    .options = (const char*[]){ __VA_ARGS__, null },        \\\n}\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_midi.h",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include <stdint.h>\n#include <errno.h>\n\n#ifdef __cplusplus\n    extern \"C\" {\n#endif\n\ntypedef struct ui_midi_s ui_midi_t;\n\ntypedef struct ui_midi_s {\n    uint8_t data[16 * 8]; // opaque implementation data\n    // must return 0 if successful or error otherwise:\n    int64_t (*notify)(ui_midi_t* midi, int64_t flags);\n} ui_midi_t;\n\ntypedef struct {\n    // flags bitset:\n    int32_t const success; // when the clip is done playing\n    int32_t const failure; // on error playing media\n    int32_t const aborted; // on stop() call\n    int32_t const superseded;\n    // midi has it's own section of legacy error messages\n    void    (*error)(errno_t r, char* s, int32_t count);\n    errno_t (*open)(ui_midi_t* midi, const char* filename);\n    errno_t (*play)(ui_midi_t* midi);\n    errno_t (*rewind)(ui_midi_t* midi);\n    errno_t (*stop)(ui_midi_t* midi);\n    errno_t (*get_volume)(ui_midi_t* midi, fp64_t *volume);\n    errno_t (*set_volume)(ui_midi_t* midi, fp64_t  volume);\n    bool    (*is_open)(ui_midi_t* midi);\n    bool    (*is_playing)(ui_midi_t* midi);\n    void    (*close)(ui_midi_t* midi);\n} ui_midi_if;\n\nextern ui_midi_if ui_midi;\n\n\n/*\n    success:\n    \"The conditions initiating the callback function have been met.\"\n    I guess meaning media is done playing...\n\n    failure:\n    \"A device error occurred while the device was executing the command.\"\n\n    aborted:\n    \"The device received a command that prevented the current\n    conditions for initiating the callback function from\n    being met. If a new command interrupts the current command\n    and it also requests notification, the device sends this\n    message only and not `superseded`\".\n    I guess meaning media is stopped playing...\n\n    superseded:\n    \"The device received another command with the \"notify\" flag set\n     and the current conditions for initiating the callback function\n     have been superseded.\"\n*/\n\n#ifdef __cplusplus\n}\n#endif\n\n\n\n"
  },
  {
    "path": "inc/ui/ui_slider.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n#include \"ui/ui_button.h\"\n\nrt_begin_c\n\ntypedef struct ui_slider_s ui_slider_t;\n\ntypedef struct ui_slider_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    int32_t step;\n    fp64_t time; // time last button was pressed\n    ui_wh_t wh;  // text measurement (special case for %0*d)\n    ui_button_t inc; // can be hidden\n    ui_button_t dec; // can be hidden\n    int32_t value;  // for ui_slider_t range slider control\n    int32_t value_min;\n    int32_t value_max;\n    // style:\n    bool notched; // true if marked with a notches and has a thumb\n} ui_slider_t;\n\nvoid ui_view_init_slider(ui_view_t* v);\n\nvoid ui_slider_init(ui_slider_t* r, const char* label, fp32_t min_w_em,\n    int32_t value_min, int32_t value_max, void (*callback)(ui_view_t* r));\n\n// ui_slider_changed can only be used on static slider variables\n\n#define ui_slider_changed(name, s, min_width_em, mn,  mx, fmt, ...) \\\n    static void name ## _changed(ui_slider_t* name) {               \\\n        (void)name; /* no warning if unused */                      \\\n        { __VA_ARGS__ }                                             \\\n    }                                                               \\\n    static                                                          \\\n    ui_slider_t name = {                                            \\\n        .view = {                                                   \\\n            .type = ui_view_slider,                                 \\\n            .init = ui_view_init_slider,                            \\\n            .fm = &ui_app.fm.prop.normal,                           \\\n            .p.text = s,                                            \\\n            .format = fmt,                                          \\\n            .callback = name ## _changed,                           \\\n            .min_w_em = min_width_em, .min_h_em = 1.25f,            \\\n            .insets  = {                                            \\\n                .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n                .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n            },                                                      \\\n            .padding = {                                            \\\n                .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n                .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n            }                                                       \\\n        },                                                          \\\n        .value_min = mn, .value_max = mx, .value = mn,              \\\n    }\n\n#define ui_slider(s, min_width_em, mn, mx, fmt, changed) {          \\\n    .view = {                                                       \\\n        .type = ui_view_slider,                                     \\\n        .init = ui_view_init_slider,                                \\\n        .fm = &ui_app.fm.prop.normal,                               \\\n        .p.text = s,                                                \\\n        .callback = changed,                                        \\\n        .format = fmt,                                              \\\n        .min_w_em = min_width_em, .min_h_em = 1.25f,                \\\n            .insets  = {                                            \\\n                .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n                .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n            },                                                      \\\n            .padding = {                                            \\\n                .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n                .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n            }                                                       \\\n    },                                                              \\\n    .value_min = mn, .value_max = mx, .value = mn,                  \\\n}\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_theme.h",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"ui/ui.h\"\n\nrt_begin_c\n\nenum {\n    ui_theme_app_mode_default     = 0,\n    ui_theme_app_mode_allow_dark  = 1,\n    ui_theme_app_mode_force_dark  = 2,\n    ui_theme_app_mode_force_light = 3\n};\n\ntypedef struct  {\n    bool (*is_app_dark)(void);\n    bool (*is_system_dark)(void);\n    bool (*are_apps_dark)(void);\n    void (*set_preferred_app_mode)(int32_t mode);\n    void (*flush_menu_themes)(void);\n    void (*allow_dark_mode_for_app)(bool allow);\n    void (*allow_dark_mode_for_window)(bool allow);\n    void (*refresh)(void);\n    void (*test)(void);\n} ui_theme_if;\n\nextern ui_theme_if ui_theme;\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_toggle.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n#include \"ui/ui_view.h\"\n\nrt_begin_c\n\ntypedef ui_view_t ui_toggle_t;\n\n// label may contain \"___\" which will be replaced with \"On\" / \"Off\"\nvoid ui_toggle_init(ui_toggle_t* b, const char* label, fp32_t ems,\n    void (*callback)(ui_toggle_t* b));\n\nvoid ui_view_init_toggle(ui_view_t* v);\n\n// ui_toggle_on_off can only be used on static toggle variables\n\n#define ui_toggle_on_off(name, s, min_width_em, ...)        \\\n    static void name ## _on_off(ui_toggle_t* name) {        \\\n        (void)name; /* no warning if unused */              \\\n        { __VA_ARGS__ }                                     \\\n    }                                                       \\\n    static                                                  \\\n    ui_toggle_t name = {                                    \\\n        .type = ui_view_toggle,                             \\\n        .init = ui_view_init_toggle,                        \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .min_w_em = min_width_em,  .min_h_em = 1.25f,       \\\n        .p.text = s,                                        \\\n        .callback = name ## _on_off,                        \\\n        .insets  = {                                        \\\n            .left  = 1.75f,        .top    = ui_view_i_tb,  \\\n            .right = ui_view_i_lr, .bottom = ui_view_i_tb   \\\n        },                                                  \\\n        .padding = {                                        \\\n            .left  = ui_view_p_lr, .top    = ui_view_p_tb,  \\\n            .right = ui_view_p_lr, .bottom = ui_view_p_tb,  \\\n        }                                                   \\\n    }\n\n#define ui_toggle(s, min_width_em, on_off) {                \\\n    .type = ui_view_toggle,                                 \\\n    .init = ui_view_init_toggle,                            \\\n    .fm = &ui_app.fm.prop.normal,                           \\\n    .p.text = s,                                            \\\n    .callback = on_off,                                     \\\n    .min_w_em = min_width_em,  .min_h_em = 1.25f,           \\\n    .insets  = {                                            \\\n        .left  = 1.75f,        .top    = ui_view_i_tb,      \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n    },                                                      \\\n    .padding = {                                            \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n    }                                                       \\\n}\n\nrt_end_c\n"
  },
  {
    "path": "inc/ui/ui_view.h",
    "content": "#pragma once\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\nenum ui_view_type_t {\n    ui_view_stack     = 'vwst',\n    ui_view_label     = 'vwlb',\n    ui_view_mbx       = 'vwmb',\n    ui_view_button    = 'vwbt',\n    ui_view_toggle    = 'vwtg',\n    ui_view_slider    = 'vwsl',\n    ui_view_image     = 'vwiv',\n    ui_view_text      = 'vwtx',\n    ui_view_span      = 'vwhs',\n    ui_view_list      = 'vwvs',\n    ui_view_spacer    = 'vwsp',\n    ui_view_scroll    = 'vwsc'\n};\n\ntypedef struct ui_view_s ui_view_t;\n\ntypedef struct ui_view_private_s { // do not access directly\n    char text[1024]; // utf8 zero terminated\n    int32_t strid;    // 0 for not yet localized, -1 no localization\n    fp64_t armed_until; // rt_clock.seconds() - when to release\n    fp64_t hover_when;  // time in seconds when to call hovered()\n    // use: ui_view.string(v) and ui_view.set_string()\n} ui_view_private_t;\n\ntypedef struct ui_view_text_metrics_s { // ui_view.measure_text() fills these attributes:\n    ui_wh_t    wh; // text width and height\n    ui_point_t xy; // text offset inside view\n    bool multiline; // text contains \"\\n\"\n} ui_view_text_metrics_t;\n\ntypedef struct ui_view_s {\n    enum ui_view_type_t type;\n    ui_view_private_t p; // private\n    void (*init)(ui_view_t* v); // called once before first layout\n    ui_view_t* parent;\n    ui_view_t* child; // first child, circular doubly linked list\n    ui_view_t* prev;  // left or top sibling\n    ui_view_t* next;  // right or top sibling\n    int32_t x;\n    int32_t y;\n    int32_t w;\n    int32_t h;\n    ui_margins_t insets;\n    ui_margins_t padding;\n    ui_view_text_metrics_t text;\n    // see ui.alignment values\n    int32_t align; // align inside parent\n    int32_t text_align; // align of the text inside control\n    int32_t max_w; // > 0 maximum width in pixels the view agrees to\n    int32_t max_h; // > 0 maximum height in pixels\n    fp32_t  min_w_em; // > 0 minimum width  of a view in \"em\"s\n    fp32_t  min_h_em; // > 0 minimum height of a view in \"em\"s\n    ui_icon_t icon; // used instead of text if != null\n    // updated on layout() call\n    const ui_fm_t* fm; // font metrics\n    int32_t  shortcut; // keyboard shortcut\n    void* that;  // for the application use\n    void (*notify)(ui_view_t* v, void* p); // for the application use\n    // two pass layout: measure() .w, .h layout() .x .y\n    // first  measure() bottom up - children.layout before parent.layout\n    // second layout() top down - parent.layout before children.layout\n    // before methods: called before measure()/layout()/paint()\n    void (*prepare)(ui_view_t* v);    // called before measure()\n    void (*measure)(ui_view_t* v);    // determine w, h (bottom up)\n    void (*measured)(ui_view_t* v);   // called after measure()\n    void (*layout)(ui_view_t* v);     // set x, y possibly adjust w, h (top down)\n    void (*composed)(ui_view_t* v);   // after layout() is done (laid out)\n    void (*erase)(ui_view_t* v);      // called before paint()\n    void (*paint)(ui_view_t* v);\n    void (*painted)(ui_view_t* v);  // called after paint()\n    // composed() is effectively called right before paint() and\n    // can be used to prepare for painting w/o need to override paint()\n    void (*debug_paint)(ui_view_t* v); // called if .debug is set to true\n    // any message:\n    bool (*message)(ui_view_t* v, int32_t message, int64_t wp, int64_t lp,\n        int64_t* rt); // return true and value in rt to stop processing\n    void (*click)(ui_view_t* v);    // ui click callback - view action\n    void (*format)(ui_view_t* v);   // format a value to text (e.g. slider)\n    void (*callback)(ui_view_t* v); // state change callback\n    void (*mouse_scroll)(ui_view_t* v, ui_point_t dx_dy); // touchpad scroll\n    void (*mouse_hover)(ui_view_t* v); // hover over\n    void (*mouse_move)(ui_view_t* v);\n    void (*double_click)(ui_view_t* v, int32_t ix);\n    // tap(ui, button_index) press(ui, button_index) see note below\n    // button index 0: left, 1: middle, 2: right\n    // bottom up (leaves to root or children to parent)\n    // return true if consumed (halts further calls up the tree)\n    bool (*tap)(ui_view_t* v, int32_t ix, bool pressed); // single click/tap inside ui\n    bool (*long_press)(ui_view_t* v, int32_t ix); // two finger click/tap or long press\n    bool (*double_tap)(ui_view_t* v, int32_t ix); // legacy double click\n    bool (*context_menu)(ui_view_t* v); // right mouse click or long press\n    void (*focus_gained)(ui_view_t* v);\n    void (*focus_lost)(ui_view_t* v);\n    // translated from key pressed/released to utf8:\n    void (*character)(ui_view_t* v, const char* utf8);\n    bool (*key_pressed)(ui_view_t* v, int64_t key);  // return true to stop\n    bool (*key_released)(ui_view_t* v, int64_t key); // processing\n    // timer() every_100ms() and every_sec() called\n    // even for hidden and disabled views\n    void (*timer)(ui_view_t* v, ui_timer_t id);\n    void (*every_100ms)(ui_view_t* v); // ~10 x times per second\n    void (*every_sec)(ui_view_t* v); // ~once a second\n    int64_t (*hit_test)(const ui_view_t* v, ui_point_t pt);\n    struct {\n        bool hidden;    // measure()/ layout() paint() is not called on\n        bool disabled;  // mouse, keyboard, key_up/down not called on\n        bool armed;     // button is pressed but not yet released\n        bool hover;     // cursor is hovering over the control\n        bool pressed;   // for ui_button_t and ui_toggle_t\n    } state;\n    // TODO: instead of flat color scheme: undefined colors for\n    // border rounded gradient etc.\n    bool flat;                // no-border appearance of controls\n    bool flip;                // flip button pressed / released\n    bool focusable;           // can be target for keyboard focus\n    bool highlightable;       // paint highlight rectangle when hover over label\n    ui_color_t color;         // interpretation depends on view type\n    int32_t    color_id;      // 0 is default meaning use color\n    ui_color_t background;    // interpretation depends on view type\n    int32_t    background_id; // 0 is default meaning use background\n    char hint[256]; // tooltip hint text (to be shown while hovering over view)\n    struct {\n        struct {\n            bool prc; // paint rect\n            bool mt;  // measure text\n        } trace;\n        struct { // after painted():\n            bool call;    // v->debug_paint()\n            bool margins; // call debug_paint_margins()\n            bool fm;      // paint font metrics\n        } paint;\n        const char* id; // for debugging purposes\n    } debug; // debug flags\n} ui_view_t;\n\n// tap() / press() APIs guarantee that single tap() is not coming\n// before fp64_t tap/click in expense of fp64_t click delay (0.5 seconds)\n// which is OK for buttons and many other UI controls but absolutely not\n// OK for text editing. Thus edit uses raw mouse events to react\n// on clicks and fp64_t clicks.\n\ntypedef struct ui_view_if {\n    // children va_args must be null terminated\n    ui_view_t* (*add)(ui_view_t* parent, ...);\n    void (*add_first)(ui_view_t* parent, ui_view_t* child);\n    void (*add_last)(ui_view_t*  parent, ui_view_t* child);\n    void (*add_after)(ui_view_t* child,  ui_view_t* after);\n    void (*add_before)(ui_view_t* child, ui_view_t* before);\n    void (*remove)(ui_view_t* v); // removes view from it`s parent\n    void (*remove_all)(ui_view_t* parent); // removes all children\n    void (*disband)(ui_view_t* parent); // removes all children recursively\n    bool (*is_parent_of)(const ui_view_t* p, const ui_view_t* c);\n    bool (*inside)(const ui_view_t* v, const ui_point_t* pt);\n    ui_ltrb_t (*margins)(const ui_view_t* v, const ui_margins_t* g); // to pixels\n    void (*inbox)(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* insets);\n    void (*outbox)(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* padding);\n    void (*set_text)(ui_view_t* v, const char* format, ...);\n    void (*set_text_va)(ui_view_t* v, const char* format, va_list va);\n    // ui_view.invalidate() prone to 30ms delays don't use in r/t video code\n    // ui_view.invalidate(v, ui_app.crc) invalidates whole client rect but\n    // ui_view.redraw() (fast non blocking) is much better instead\n    void (*invalidate)(const ui_view_t* v, const ui_rect_t* rect_or_null);\n    bool (*is_orphan)(const ui_view_t* v);   // view parent chain has null\n    bool (*is_hidden)(const ui_view_t* v);   // view or any parent is hidden\n    bool (*is_disabled)(const ui_view_t* v); // view or any parent is disabled\n    bool (*is_control)(const ui_view_t* v);\n    bool (*is_container)(const ui_view_t* v);\n    bool (*is_spacer)(const ui_view_t* v);\n    const char* (*string)(ui_view_t* v);  // returns localized text\n    void (*timer)(ui_view_t* v, ui_timer_t id);\n    void (*every_sec)(ui_view_t* v);\n    void (*every_100ms)(ui_view_t* v);\n    int64_t (*hit_test)(const ui_view_t* v, ui_point_t pt);\n    // key_pressed() key_released() return true to stop further processing\n    bool (*key_pressed)(ui_view_t* v, int64_t v_key);\n    bool (*key_released)(ui_view_t* v, int64_t v_key);\n    void (*character)(ui_view_t* v, const char* utf8);\n    void (*paint)(ui_view_t* v);\n    bool (*has_focus)(const ui_view_t* v); // ui_app.focused() && ui_app.focus == v\n    void (*set_focus)(ui_view_t* view_or_null);\n    void (*lose_hidden_focus)(ui_view_t* v);\n    void (*hovering)(ui_view_t* v, bool start);\n    void (*mouse_hover)(ui_view_t* v); // hover over\n    void (*mouse_move)(ui_view_t* v);\n    void (*mouse_scroll)(ui_view_t* v, ui_point_t dx_dy); // touchpad scroll\n    ui_wh_t (*text_metrics_va)(int32_t x, int32_t y, bool multiline, int32_t w,\n        const ui_fm_t* fm, const char* format, va_list va);\n    ui_wh_t (*text_metrics)(int32_t x, int32_t y, bool multiline, int32_t w,\n        const ui_fm_t* fm, const char* format, ...);\n    void (*text_measure)(ui_view_t* v, const char* s,\n        ui_view_text_metrics_t* tm);\n    void (*text_align)(ui_view_t* v, ui_view_text_metrics_t* tm);\n    void (*measure_text)(ui_view_t* v); // fills v->text.mt and .xy\n    // measure_control(): control is special case with v->text.mt and .xy\n    void (*measure_control)(ui_view_t* v);\n    void (*measure_children)(ui_view_t* v);\n    void (*layout_children)(ui_view_t* v);\n    void (*measure)(ui_view_t* v);\n    void (*layout)(ui_view_t* v);\n    void (*hover_changed)(ui_view_t* v);\n    bool (*is_shortcut_key)(ui_view_t* v, int64_t key);\n    bool (*context_menu)(ui_view_t* v);\n    // `ix` 0: left 1: middle 2: right\n    bool (*tap)(ui_view_t* v, int32_t ix, bool pressed);\n    bool (*long_press)(ui_view_t* v, int32_t ix);\n    bool (*double_tap)(ui_view_t* v, int32_t ix);\n    bool (*message)(ui_view_t* v, int32_t m, int64_t wp, int64_t lp, int64_t* ret);\n    void (*debug_paint_margins)(ui_view_t* v); // insets padding\n    void (*debug_paint_fm)(ui_view_t* v);   // text font metrics\n    void (*test)(void);\n} ui_view_if;\n\nextern ui_view_if ui_view;\n\n// view children iterator:\n\n#define ui_view_for_each_begin(v, it) do {       \\\n    ui_view_t* it = (v)->child;                  \\\n    if (it != null) {                            \\\n        do {                                     \\\n\n\n#define ui_view_for_each_end(v, it)              \\\n            it = it->next;                       \\\n        } while (it != (v)->child);              \\\n    }                                            \\\n} while (0)\n\n#define ui_view_for_each(v, it, ...) \\\n    ui_view_for_each_begin(v, it)    \\\n    { __VA_ARGS__ }                  \\\n    ui_view_for_each_end(v, it)\n\n#define ui_view_debug_id(v) \\\n    ((v)->debug.id != null ? (v)->debug.id : (v)->p.text)\n\n// #define code(statements) statements\n//\n// used as:\n// {\n//     macro({\n//        foo();\n//        bar();\n//     })\n// }\n//\n// except in m4 preprocessor loses new line\n// between foo() and bar() and makes debugging and\n// using __LINE__ difficult to impossible.\n//\n// Also\n// #define code(...) { __VA_ARGS__ }\n// is way easier on preprocessor\n\n// ui_view_insets (fractions of 1/2 to keep float calculations precise):\n#define ui_view_i_lr (0.750f) // 3/4 of \"em.w\" on left and right\n#define ui_view_i_tb (0.125f) // 1/8 em\n\n// ui_view_padding\n#define ui_view_p_lr (0.375f)\n#define ui_view_p_tb (0.250f)\n\n#define ui_view_call_init(v) do {                   \\\n    if ((v)->init != null) {                        \\\n        void (*_init_)(ui_view_t* _v_) = (v)->init; \\\n        (v)->init = null; /* before! call */        \\\n        _init_((v));                                \\\n    }                                               \\\n} while (0)\n\n\nrt_end_c\n"
  },
  {
    "path": "inc/ut_std.h",
    "content": "#pragma once\n#include <ctype.h>\n#include <errno.h>\n#include <float.h>\n#include <limits.h>\n#include <locale.h>\n#include <malloc.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define rt_stringify(x) #x\n#define rt_tostring(x) rt_stringify(x)\n#define rt_pragma(x) _Pragma(rt_tostring(x))\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#pragma GCC diagnostic ignored \"-Wfour-char-constants\"\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#pragma GCC diagnostic ignored \"-Wmissing-noreturn\"\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"\n#pragma GCC diagnostic ignored \"-Wcast-align\"\n#pragma GCC diagnostic ignored \"-Waddress-of-packed-member\"\n#pragma GCC diagnostic ignored \"-Wused-but-marked-unused\" // because in debug only\n#define rt_msvc_pragma(x)\n#define rt_gcc_pragma(x) rt_pragma(x)\n#else\n#define rt_gcc_pragma(x)\n#define rt_msvc_pragma(x) rt_pragma(x)\n#endif\n\n#ifdef _MSC_VER\n    #define rt_suppress_constant_cond_exp _Pragma(\"warning(suppress: 4127)\")\n#else\n    #define rt_suppress_constant_cond_exp\n#endif\n\n// Type aliases for floating-point types similar to <stdint.h>\ntypedef float  fp32_t;\ntypedef double fp64_t;\n// \"long fp64_t\" is required by C standard but the bitness\n// of it is not specified.\n\n#ifdef __cplusplus\n    #define rt_begin_c extern \"C\" {\n    #define rt_end_c } // extern \"C\"\n#else\n    #define rt_begin_c // C headers compiled as C++\n    #define rt_end_c\n#endif\n\n// rt_countof() and rt_countof() are suitable for\n// small < 2^31 element arrays\n\n#define rt_countof(a) ((int32_t)((int)(sizeof(a) / sizeof((a)[0]))))\n\n#if defined(__GNUC__) || defined(__clang__)\n    #define rt_force_inline __attribute__((always_inline))\n#elif defined(_MSC_VER)\n    #define rt_force_inline __forceinline\n#endif\n\n#ifndef __cplusplus\n    #define null ((void*)0) // better than NULL which is zero\n#else\n    #define null nullptr\n#endif\n\n#if defined(_MSC_VER)\n    #define rt_thread_local __declspec(thread)\n#else\n    #ifndef __cplusplus\n        #define rt_thread_local _Thread_local // C99\n    #else\n        // C++ supports rt_thread_local keyword\n    #endif\n#endif\n\n// rt_begin_packed rt_end_packed\n// usage: typedef rt_begin_packed struct foo_s { ... } rt_end_packed foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_attribute_packed __attribute__((packed))\n#define rt_begin_packed\n#define rt_end_packed rt_attribute_packed\n#else\n#define rt_begin_packed rt_pragma( pack(push, 1) )\n#define rt_end_packed rt_pragma( pack(pop) )\n#define rt_attribute_packed\n#endif\n\n// usage: typedef struct rt_aligned_8 foo_s { ... } foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_aligned_8 __attribute__((aligned(8)))\n#elif defined(_MSC_VER)\n#define rt_aligned_8 __declspec(align(8))\n#else\n#define rt_aligned_8\n#endif\n\n\n// In callbacks the formal parameters are\n// frequently unused. Also sometimes parameters\n// are used in debug configuration only (e.g. rt_assert() checks)\n// but not in release.\n// C does not have anonymous parameters like C++\n// Instead of:\n//      void foo(param_type_t param) { (void)param; / *unused */ }\n// use:\n//      vod foo(param_type_t rt_unused(param)) { }\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_unused(name) name __attribute__((unused))\n#elif defined(_MSC_VER)\n#define rt_unused(name) _Pragma(\"warning(suppress:  4100)\") name\n#else\n#define rt_unused(name) name\n#endif\n\n// Because MS C compiler is unhappy about alloca() and\n// does not implement (C99 optional) dynamic arrays on the stack:\n\n#define rt_stackalloc(n) (_Pragma(\"warning(suppress: 6255 6263)\") alloca(n))\n\n// alloca() is messy and in general is a not a good idea.\n// try to avoid if possible. Stack sizes vary from 64KB to 8MB in 2024."
  },
  {
    "path": "inc/ut_win32.h",
    "content": "#pragma once\n#ifdef WIN32\n\n#pragma warning(push)\n#pragma warning(disable: 4255) // no function prototype: '()' to '(void)'\n#pragma warning(disable: 4459) // declaration of '...' hides global declaration\n\n#pragma push_macro(\"UNICODE\")\n#define UNICODE // always because otherwise IME does not work\n\n// ut:\n#include <Windows.h>  // used by:\n#include <Psapi.h>    // both rt_loader.c and rt_processes.c\n#include <shellapi.h> // rt_processes.c\n#include <winternl.h> // rt_processes.c\n#include <initguid.h>     // for knownfolders\n#include <KnownFolders.h> // rt_files.c\n#include <AclAPI.h>       // rt_files.c\n#include <ShlObj_core.h>  // rt_files.c\n#include <Shlwapi.h>      // rt_files.c\n// ui:\n#include <commdlg.h>\n#include <dbghelp.h>\n#include <dwmapi.h>\n#include <imm.h>\n#include <ShellScalingApi.h>\n#include <tlhelp32.h>\n#include <VersionHelpers.h>\n#include <windowsx.h>\n#include <winnt.h>\n\n#pragma pop_macro(\"UNICODE\")\n\n#pragma warning(pop)\n\n#include <fcntl.h>\n\n#define rt_export __declspec(dllexport)\n\n// Win32 API BOOL -> errno_t translation\n\n#define rt_b2e(call) ((errno_t)(call ? 0 : GetLastError()))\n\nvoid rt_win32_close_handle(void* h);\n/* translate ix to error */\nerrno_t rt_wait_ix2e(uint32_t r);\n\n\n#endif // WIN32\n"
  },
  {
    "path": "msvc2022/.editorconfig",
    "content": "# top-most EditorConfig file for this level\nroot = true\n\n[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]\n\nspelling_languages = en-us\nspelling_checkable_types = strings,identifiers,comments\nspelling_exclusion_path = .\\exclusion.dic\nspelling_error_severity = hint\n\n# Visual C++ Code Style settings\n\ncpp_generate_documentation_comments = none\n\n# Visual C++ Formatting settings\n\ncpp_indent_braces = false\ncpp_indent_multi_line_relative_to = innermost_parenthesis\ncpp_indent_within_parentheses = indent\ncpp_indent_preserve_within_parentheses = true\ncpp_indent_case_contents = true\ncpp_indent_case_labels = true\ncpp_indent_case_contents_when_block = false\ncpp_indent_lambda_braces_when_parameter = true\ncpp_indent_goto_labels = one_left\ncpp_indent_preprocessor = none\ncpp_indent_access_specifiers = false\ncpp_indent_namespace_contents = true\ncpp_indent_preserve_comments = true\ncpp_new_line_before_open_brace_namespace = ignore\ncpp_new_line_before_open_brace_type = ignore\ncpp_new_line_before_open_brace_function = ignore\ncpp_new_line_before_open_brace_block = ignore\ncpp_new_line_before_open_brace_lambda = ignore\ncpp_new_line_scope_braces_on_separate_lines = false\ncpp_new_line_close_brace_same_line_empty_type = true\ncpp_new_line_close_brace_same_line_empty_function = true\ncpp_new_line_before_catch = false\ncpp_new_line_before_else = false\ncpp_new_line_before_while_in_do_while = false\ncpp_space_before_function_open_parenthesis = remove\ncpp_space_within_parameter_list_parentheses = false\ncpp_space_between_empty_parameter_list_parentheses = false\ncpp_space_after_keywords_in_control_flow_statements = true\ncpp_space_within_control_flow_statement_parentheses = false\ncpp_space_before_lambda_open_parenthesis = false\ncpp_space_within_cast_parentheses = false\ncpp_space_after_cast_close_parenthesis = false\ncpp_space_within_expression_parentheses = false\ncpp_space_before_block_open_brace = true\ncpp_space_between_empty_braces = false\ncpp_space_before_initializer_list_open_brace = false\ncpp_space_within_initializer_list_braces = true\ncpp_space_preserve_in_initializer_list = true\ncpp_space_before_open_square_bracket = false\ncpp_space_within_square_brackets = false\ncpp_space_before_empty_square_brackets = false\ncpp_space_between_empty_square_brackets = false\ncpp_space_group_square_brackets = true\ncpp_space_within_lambda_brackets = false\ncpp_space_between_empty_lambda_brackets = false\ncpp_space_before_comma = false\ncpp_space_after_comma = true\ncpp_space_remove_around_member_operators = true\ncpp_space_before_inheritance_colon = true\ncpp_space_before_constructor_colon = true\ncpp_space_remove_before_semicolon = true\ncpp_space_after_semicolon = true\ncpp_space_remove_around_unary_operator = true\ncpp_space_around_binary_operator = insert\ncpp_space_around_assignment_operator = insert\ncpp_space_pointer_reference_alignment = left\ncpp_space_around_ternary_operator = insert\ncpp_use_unreal_engine_macro_formatting = true\ncpp_wrap_preserve_blocks = one_liners\n\n# Visual C++ Inlcude Cleanup settings\n\ncpp_include_cleanup_add_missing_error_tag_type = suggestion\ncpp_include_cleanup_remove_unused_error_tag_type = dimmed\ncpp_include_cleanup_optimize_unused_error_tag_type = suggestion\ncpp_include_cleanup_sort_after_edits = false\ncpp_sort_includes_error_tag_type = none\ncpp_sort_includes_priority_case_sensitive = false\ncpp_sort_includes_priority_style = quoted\ncpp_includes_style = default\ncpp_includes_use_forward_slash = true\n"
  },
  {
    "path": "msvc2022/amalgamate.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{1EA9BF0C-402B-4852-BD16-644244F0D1B9}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>amalgamate</RootNamespace>\n    <ProjectName>amalgamate</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n    <PostBuildEvent>\n      <Command>rem pushd $(ProjectDir).. &amp;&amp; $(TargetPath) ut &gt; $(ProjectDir)..\\sfh\\ut.h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n    <PostBuildEvent>\n      <Command>rem pushd $(ProjectDir).. &amp;&amp; $(TargetPath) ut &gt; $(ProjectDir)..\\sfh\\ut.h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n    <PostBuildEvent>\n      <Command>rem pushd $(ProjectDir).. &amp;&amp; $(TargetPath) ut &gt; $(ProjectDir)..\\sfh\\ut.h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n    <PostBuildEvent>\n      <Command>rem pushd $(ProjectDir).. &amp;&amp; $(TargetPath) ut &gt; $(ProjectDir)..\\sfh\\ut.h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\tools\\amalgamate.c\" />\n    <ClCompile Include=\"..\\src\\tools\\dirent.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\tools\\dirent.h\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n</Project>"
  },
  {
    "path": "msvc2022/amalgamate.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\tools\\amalgamate.c\" />\n    <ClCompile Include=\"..\\src\\tools\\dirent.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\tools\\dirent.h\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/common.props",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ImportGroup Label=\"PropertySheets\" />\n  <ItemDefinitionGroup>\n    <ClCompile>\n      <AdditionalIncludeDirectories>$(ProjectDir)..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>STRICT;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>VC_EXTRALEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <WarningLevel>EnableAllWarnings</WarningLevel>\n      <DisableSpecificWarnings>4710;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- function not inlined -->\n      <DisableSpecificWarnings>4711;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- automatic inline expansion -->\n      <DisableSpecificWarnings>4820;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- '?' bytes padding added after data member -->\n      <DisableSpecificWarnings>5045;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- Spectre mitigation -->\n      <DisableSpecificWarnings>6262;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- EXCESSIVESTACKUSAGE -->\n      <DisableSpecificWarnings>4746;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <!-- volatile access of 'var' is subject to /volatile:<iso|ms>\n            setting; consider using __iso_volatile_load/store intrinsic functions -->\n      <!-- Theat these warning as errors (because they are): -->\n      <TreatSpecificWarningsAsErrors>4133;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- incompatible types -->\n      <TreatSpecificWarningsAsErrors>4020;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- too many actual parameters -->\n      <TreatSpecificWarningsAsErrors>4716;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- function must return a value -->\n      <TreatSpecificWarningsAsErrors>4013;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- 'foo' undefined; assuming extern returning int -->\n      <TreatSpecificWarningsAsErrors>4312;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- conversion from '...' to '...' of greater size -->\n      <TreatSpecificWarningsAsErrors>4087;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- 'args': declared with 'void' parameter list -->\n      <TreatSpecificWarningsAsErrors>4033;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- function 'foo' must return a value -->\n      <TreatSpecificWarningsAsErrors>4098;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- 'void' function returning a value -->\n      <TreatSpecificWarningsAsErrors>4047;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- '=': '...' differs in levels of indirection -->\n      <TreatSpecificWarningsAsErrors>4057;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- function type differs in indirection to slightly different base types -->\n      <TreatSpecificWarningsAsErrors>4113;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- function '...' differs in parameter lists from '...' -->\n      <TreatSpecificWarningsAsErrors>4700;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- used uninitialized local variable '...' -->\n      <TreatSpecificWarningsAsErrors>4431;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>\n      <!-- missing type specifier - int assumed. Note: C no longer supports default-int -->\n      <DebugInformationFormat>OldStyle</DebugInformationFormat>\n      <AdditionalOptions>/std:c17 /volatile:ms /experimental:c11atomics %(AdditionalOptions)</AdditionalOptions>\n    </ClCompile>\n    <Link>\n      <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>\n      <AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <ResourceCompile>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\inc;$(ProjectDir)..\\src;$(ProjectDir)..\\src\\samples</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>NDEBUG;$(DefineConstants);%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ResourceCompile>\n  </ItemDefinitionGroup>\n  <PropertyGroup Condition=\"'$(Configuration)'=='debug'\">\n    <OutDir>$(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\build\\$(Configuration)\\$(Platform)\\$(ProjectName)\\</IntDir>\n    <GenerateManifest>false</GenerateManifest>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <Optimization>Disabled</Optimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)'=='release'\">\n    <OutDir>$(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\build\\$(Configuration)\\$(Platform)\\$(ProjectName)\\</IntDir>\n    <GenerateManifest>false</GenerateManifest>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <Optimization>Full</Optimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup>\n    <LinkIncremental>false</LinkIncremental>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)'=='debug'\">\n    <ClCompile>\n      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <LanguageStandard_C>stdc17</LanguageStandard_C>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <Optimization>Disabled</Optimization>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <ResourceCompile>\n      <PreprocessorDefinitions>_DEBUG;DEBUG;$(DefineConstants);%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <NullTerminateStrings>true</NullTerminateStrings>\n    </ResourceCompile>\n    <Link>\n      <OptimizeReferences>false</OptimizeReferences>\n      <LinkIncremental>false</LinkIncremental>\n      <AssemblyDebug>true</AssemblyDebug>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)'=='release'\">\n    <ClCompile>\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <LanguageStandard_C>stdc17</LanguageStandard_C>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n      <Optimization>Full</Optimization>\n      <WholeProgramOptimization>true</WholeProgramOptimization>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n    </ClCompile>\n    <ResourceCompile>\n      <PreprocessorDefinitions>NDEBUG</PreprocessorDefinitions>\n      <NullTerminateStrings>true</NullTerminateStrings>\n    </ResourceCompile>\n    <Link>\n      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>\n      <OptimizeReferences>true</OptimizeReferences>\n      <LinkIncremental>false</LinkIncremental>\n      <AssemblyDebug>true</AssemblyDebug>\n    </Link>\n  </ItemDefinitionGroup>\n</Project>"
  },
  {
    "path": "msvc2022/exclusion.dic",
    "content": "#### .editorconfig seems to be ignored\n#### merge manually for not with:\n#### C:\\Users\\leo\\AppData\\Local\\Microsoft\\VisualStudio\\17.0_0790c7c1\\exclusion.dic\nDacl\nHant\nHilight\nHola\nInteli\nIntelisense\nIntelli\ndxgi\ndwrite\nswapchain\nrect\ntraceln\nidof\ndism\nsegoe\nPhur\nntdll\ndoserrno\ncountof\nhcore\nshcore\nfdips\nfdip\nbgra\nalice\nindian\nnavajo\ntabstop\ngainsboro\nperu\nargb\nzoomer\nltrb\nprintfs\ndebugln\nKuznetsov\ntrols\nprintln\nperrno\nmuldiv\ndbghelp\ncomctl\ncomdlg\ndwmapi\nmsimg\nuxtheme\nToolhelp\nstrpintf\nLatn\ndefau\nFormee\ncleartype\nrgbx\nbgrx\nHilight\nwingdi\nprussian\ncharleston\nfogra\nklein\nzaffre\nfalu\nbyzantium\njazzberry\nfrench\nsacramento\npersian\ntimberwolf\npayne\nibeam\nnwse\nnesw\nrelayout\nfuzzer\nadvapi\npsapi\nshlwapi\nimagehlp\nposix\nmkdirs\nrmdirs\nunmap\nhwair\ndword\nsqlite\notms\nrgrc\nIntelli\negypt\nsfence\nrespone\nabcdefghi\nmollit\nanim\ncupidatat\ncommodo\naute\namet\nconsectetur\nadipiscing\nelit\neiusmod\ntempor\nlaborum\ndeserunt\noccaecat\nvoluptate\nveniam\nquis\nincididunt\nnostrud\nullamco\naliqua\nlaboris\naliquip\nconsequat\nirure\nreprehenderit\nenim\nvelit\nproident\nesse\ncillum\nfugiat\nExcepteur\nnulla\nofficia\npariatur\npellentesque\nsapien\nfaucibus\nquisque\npretium\nduis\nlacus\nfringilla\nvivamus\nplacerat\ntellus\nnisl\nmassa\niaculis\nmetus\negestas\nbibendum\nurna\ncurabitur\nfacilisi\ncubilia\ncurae\nhabitasse\nplatea\ndictumst\nmorbi\nsenectus\nnetus\nsuscipit\nfeugiat\ntristique\naccumsan\nmaecenas\npotenti\nultricies\npraesent\nfelis\nvenenatis\nultrices\nproin\nprimis\nvulputate\nornare\nsagittis\nvehicula\nullamcorper\nrutrum\ncras\neleifend\nturpis\nimperdiet\nmollis\nnullam\nvolutpat\nporttitor\nlectus\naugue\narcu\ndignissim\naliquam\nelementum\neuismod\nquam\njusto\ncongue\nsollicitudin\nerat\nviverra\ntincidunt\nfacilisis\ndapibus\netiam\ninterdum\ncondimentum\nneque\ntortor\nluctus\nnibh\nfinibus\naenean\nmalesuada\nscelerisque\nnunc\nposuere\nhendrerit\naptent\ntaciti\nsociosqu\nlitora\ntorquent\nconubia\ninceptos\nhimenaeos\norci\nvarius\npurus\nnatoque\npenatibus\nmagnis\nmontes\nnascetur\nridiculus\ndonec\nrhoncus\nlobortis\nmolestie\nmattis\neget\nodio\nphasellus\nefficitur\nlaoreet\nmauris\nfusce\nrisus\nblandit\nsuspendisse\naliquet\nsodales\npgup\npgdw\nistarts\niends\nchinese\n"
  },
  {
    "path": "msvc2022/groot.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>groot</RootNamespace>\n    <ProjectName>groot</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n    <Manifest />\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n    <Manifest />\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n    <ClInclude Include=\"..\\src\\samples\\groot\\groot.h\" />\n    <ClInclude Include=\"..\\src\\samples\\groot\\rocket.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\stb_image.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\groot\\groot.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\groot\\groot.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <UseLibraryDependencyInputs>true</UseLibraryDependencyInputs>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\groot\\groot.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/groot.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n    <ClInclude Include=\"..\\src\\samples\\groot\\rocket.h\" />\n    <ClInclude Include=\"..\\src\\samples\\groot\\groot.h\" />\n    <ClInclude Include=\"..\\src\\samples\\stb_image.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\groot\\groot.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\groot\\groot.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\groot\\groot.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/manifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- Support for Windows Vista\n      <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n           Support for Windows 7\n      <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n           Support for Windows 8\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/>\n           Support for Windows 8.1\n      <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/>\n      -->\n      <!-- Support for Windows 10 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/>\n    </application>\n  </compatibility>\n  <application>\n    <windowsSettings>\n      <activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>\n    </windowsSettings>\n  </application>\n</assembly>"
  },
  {
    "path": "msvc2022/msvc2022.vssettings",
    "content": "<UserSettings><ApplicationIdentity version=\"17.0\"/><ToolsOptions><ToolsOptionsCategory name=\"Environment\" RegisteredName=\"Environment\"><ToolsOptionsSubCategory name=\"Documents\" RegisteredName=\"Documents\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"ShowMiscFilesProject\">false</PropertyValue><PropertyValue name=\"AutoloadExternalChanges\">true</PropertyValue><PropertyValue name=\"CheckForConsistentLineEndings\">true</PropertyValue><PropertyValue name=\"SaveDocsAsUnicodeWhenDataLoss\">false</PropertyValue><PropertyValue name=\"InitializeOpenFileFromCurrentDocument\">true</PropertyValue><PropertyValue name=\"ReuseSavedActiveDocWindow\">false</PropertyValue><PropertyValue name=\"DetectFileChangesOutsideIDE\">true</PropertyValue><PropertyValue name=\"DontShowGlobalUndoChangeLossDialog\">true</PropertyValue><PropertyValue name=\"AllowEditingReadOnlyFiles\">true</PropertyValue><PropertyValue name=\"DocumentDockPreference\">0</PropertyValue><PropertyValue name=\"MiscFilesProjectSavesLastNItems\">0</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"FindAndReplace\" RegisteredName=\"FindAndReplace\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"ShowWarningMessages\">true</PropertyValue><PropertyValue name=\"InitializeFromEditor\">true</PropertyValue><PropertyValue name=\"ShowMessageBoxes\">false</PropertyValue><PropertyValue name=\"HideWindowAfterMatchFromQuickFindReplace\">false</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"Import and Export Settings\" RegisteredName=\"Import and Export Settings\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"TrackTeamSettings\">false</PropertyValue><PropertyValue name=\"TeamSettingsFile\">0</PropertyValue><PropertyValue name=\"AutoSaveFile\">%vsspv_vs_localappdata_dir%\\settings\\CurrentSettings.vssettings</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"ProductUpdates\" RegisteredName=\"ProductUpdates\" PackageName=\"SetupCompositionPackage\"><PropertyValue name=\"OperationMode\">InstallWhileDownloading</PropertyValue><PropertyValue name=\"IsBackgroundDownloadEnabled\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"ProjectsAndSolution\" RegisteredName=\"ProjectsAndSolution\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"ProjectsLocation\">%vsspv_user_appdata%\\source\\repos</PropertyValue><PropertyValue name=\"PromptForRenameSymbol\">true</PropertyValue><PropertyValue name=\"TrackFileSelectionInExplorer\">false</PropertyValue><PropertyValue name=\"ShowOutputWindowBeforeBuild\">true</PropertyValue><PropertyValue name=\"OnRunWhenErrors\">2</PropertyValue><PropertyValue name=\"OnRunWhenOutOfDate\">1</PropertyValue><PropertyValue name=\"AlwaysShowSolutionNode\">true</PropertyValue><PropertyValue name=\"ProjectItemTemplatesLocation\">%vsspv_visualstudio_dir%\\Templates\\ItemTemplates</PropertyValue><PropertyValue name=\"OnlySaveStartupProjectsAndDependencies\">true</PropertyValue><PropertyValue name=\"ShowAdvancedBuildConfigurations\">true</PropertyValue><PropertyValue name=\"DefaultBehaviorForStartupProjects\">1</PropertyValue><PropertyValue name=\"OnRunOrPreview\">0</PropertyValue><PropertyValue name=\"MSBuildOutputVerbosity\">1</PropertyValue><PropertyValue name=\"ProjectTemplatesLocation\">%vsspv_visualstudio_dir%\\Templates\\ProjectTemplates</PropertyValue><PropertyValue name=\"ShowTaskListAfterBuild\">false</PropertyValue><PropertyValue name=\"ConcurrentBuilds\">4</PropertyValue><PropertyValue name=\"SaveNewProjects\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"RoamingSettings\" RegisteredName=\"RoamingSettings\" PackageName=\"Roaming Manager Package\"><PropertyValue name=\"GitHubEnterpriseEnabled\">false</PropertyValue><PropertyValue name=\"RoamingEnabled\">true</PropertyValue><PropertyValue name=\"SelectedFlowType\">1</PropertyValue><PropertyValue name=\"SignInAllTenants\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"Startup\" RegisteredName=\"Startup\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"OnStartUp\">3</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"TaskList\" RegisteredName=\"TaskList\" PackageName=\"ErrorListPackage\"><PropertyValue name=\"ConfirmTaskDeletion\">false</PropertyValue><PropertyValue name=\"CommentTokens\" ArrayType=\"VT_BSTR\" ArrayElementCount=\"4\"><PropertyValue name=\"0\">HACK:2</PropertyValue><PropertyValue name=\"1\">TODO:2</PropertyValue><PropertyValue name=\"2\">UNDONE:2</PropertyValue><PropertyValue name=\"3\">UnresolvedMergeConflict:3</PropertyValue></PropertyValue><PropertyValue name=\"WarnOnAddingHiddenItem\">false</PropertyValue><PropertyValue name=\"DontShowFilePaths\">false</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"WebBrowser\" RegisteredName=\"WebBrowser\" PackageName=\"Visual Studio Web Browser Package\">\n\t\t\t\t<PropertyValue name=\"HomePage\">https://go.microsoft.com/fwlink/?LinkId=32722&amp;clcid=%VSSPV_LCID_HEX%</PropertyValue>\n\t\t\t\t<PropertyValue name=\"SearchPage\">https://go.microsoft.com/fwlink/?LinkId=32722&amp;clcid=%VSSPV_LCID_HEX%</PropertyValue>\n\t\t\t\t<PropertyValue name=\"ViewSourceExternalProgram\">%systemroot%\\system32\\notepad.exe</PropertyValue>\n\t\t\t\t<PropertyValue name=\"ViewSourceIn\">1</PropertyValue>\n\t\t\t</ToolsOptionsSubCategory></ToolsOptionsCategory><ToolsOptionsCategory name=\"Projects\" RegisteredName=\"Projects\"><ToolsOptionsSubCategory name=\"VBDefaults\" RegisteredName=\"VBDefaults\" PackageName=\"Visual Basic Project System\">\n\t\t\t\t<PropertyValue name=\"OptionCompare\">0</PropertyValue>\n\t\t\t\t<PropertyValue name=\"OptionExplicit\">1</PropertyValue>\n\t\t\t\t<PropertyValue name=\"OptionInfer\">1</PropertyValue>\n\t\t\t\t<PropertyValue name=\"OptionStrict\">0</PropertyValue>\n\t\t\t</ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"VCGeneral\" RegisteredName=\"VCGeneral\" PackageName=\"Visual C++ Project System Package\"><PropertyValue name=\"ExtensionsToInclude\">.cpp;.cxx;.cc;.c;.c++;.cppm;.ixx;.inl;.ipp;.h;.hh;.hpp;.hxx;.h++;.hm;.inc;.rc;.resx;.idl;.rc2;.def;.odl;.asm;.asmx;.xsd;.bin;.rgs;.html;.htm;.manifest</PropertyValue><PropertyValue name=\"EnableProjectCaching\">true</PropertyValue><PropertyValue name=\"ToolFileSearchPaths\">0</PropertyValue><PropertyValue name=\"ShowEnvironmentInBuildLog\">false</PropertyValue><PropertyValue name=\"ExtensionsToHide\">.suo;.sln;.ncb;.sdf;.vcxproj;.csproj;.user;.vbproj;.scc;.vsscc;.vspscc;.old;.filters</PropertyValue><PropertyValue name=\"EnableProjectLoadUIRefresh\">false</PropertyValue><PropertyValue name=\"DefaultSolutionExplorerMode\">0</PropertyValue><PropertyValue name=\"BuildTiming\">false</PropertyValue><PropertyValue name=\"MaxConcurrentFileCompiles\">0</PropertyValue><PropertyValue name=\"BuildLogging\">true</PropertyValue></ToolsOptionsSubCategory></ToolsOptionsCategory><ToolsOptionsCategory name=\"TextEditor\" RegisteredName=\"TextEditor\"><ToolsOptionsSubCategory name=\"AllLanguages\" RegisteredName=\"AllLanguages\" PackageName=\"Text Management Package\"/><ToolsOptionsSubCategory name=\"Basic\" RegisteredName=\"Basic\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">false</PropertyValue><PropertyValue name=\"ShowNavigationBar\">true</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"Basic-Specific\" RegisteredName=\"Basic-Specific\" PackageName=\"VisualBasicPackage\">\n\t\t\t\t<PropertyValue name=\"AutoComment\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"AutoEndInsert\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"AutoRequiredMemberInsert\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"BasicClosedFileDiagnostics\">-1</PropertyValue>\n\t\t\t\t<PropertyValue name=\"ClosedFileDiagnostics\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"DisplayLineSeparators\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"EnableHighlightReferences\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"EnableHighlightRelatedKeywords\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"ExtractMethod_AllowMovingDeclaration\">false</PropertyValue>\n\t\t\t\t<PropertyValue name=\"ExtractMethod_DoNotPutOutOrRefOnStruct\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Option_PlaceSystemNamespaceFirst\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Option_ShowItemsFromUnimportedNamespaces\">-1</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Option_SuggestImportsForTypesInNuGetPackages\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Option_SuggestImportsForTypesInReferenceAssemblies\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Outlining\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"PrettyListing\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"RenameTrackingPreview\">true</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferCoalesceExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferCollectionInitializer\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferExplicitTupleNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferInferredAnonymousTypeMemberNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferInferredTupleNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferInlinedVariableDeclaration\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferNullPropagation\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferObjectInitializer\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferReadonly\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_PreferThrowExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_QualifyEventAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_QualifyFieldAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_QualifyMethodAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t\t<PropertyValue name=\"Style_QualifyPropertyAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue>\n\t\t\t</ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"C/C++\" RegisteredName=\"C/C++\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">true</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">false</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"C/C++ Specific\" RegisteredName=\"C/C++ Specific\" PackageName=\"Visual C++ Language Manager Package\"><PropertyValue name=\"EnableDetailedPoirotLogs\">false</PropertyValue><PropertyValue name=\"TaskScopeHideSolutionFile\">false</PropertyValue><PropertyValue name=\"ContinueSingleLineComments\">true</PropertyValue><PropertyValue name=\"IncludeCleanupRemoveUnusedErrorTagType\">4</PropertyValue><PropertyValue name=\"ExpandSelectionForSurroundScope\">true</PropertyValue><PropertyValue name=\"EnableGotoDefCounterparts\">true</PropertyValue><PropertyValue name=\"DisableCurrentItemSelection\">true</PropertyValue><PropertyValue name=\"DisableAggressiveMemberList\">false</PropertyValue><PropertyValue name=\"MaximumCachedTranslationUnits\">7</PropertyValue><PropertyValue name=\"SpaceAroundBinaryOperator\">2</PropertyValue><PropertyValue name=\"SpaceAfterSemicolon\">true</PropertyValue><PropertyValue name=\"PreserveInitListSpace\">true</PropertyValue><PropertyValue name=\"SpaceBeforeFunctionParenthesis2\">1</PropertyValue><PropertyValue name=\"NewlineFunctionBrace\">0</PropertyValue><PropertyValue name=\"IndentBlockContents\">false</PropertyValue><PropertyValue name=\"InlineHintsEnabled_Exp\">false</PropertyValue><PropertyValue name=\"EnableLinter\">false</PropertyValue><PropertyValue name=\"ReferencesHideInCommentResults\">true</PropertyValue><PropertyValue name=\"ReferencesHideUnconfirmedResults\">true</PropertyValue><PropertyValue name=\"SortIncludesErrorTagType\">0</PropertyValue><PropertyValue name=\"RefactorScope\">2</PropertyValue><PropertyValue name=\"RecreateDatabase\">false</PropertyValue><PropertyValue name=\"MaximumFindReferencesThreads\">3</PropertyValue><PropertyValue name=\"DisableHLSLIntelliSense\">true</PropertyValue><PropertyValue name=\"MemberListFilterPredictive\">false</PropertyValue><PropertyValue name=\"UseTabToInsertSnippet\">true</PropertyValue><PropertyValue name=\"SpaceBeforeInitListBrace\">false</PropertyValue><PropertyValue name=\"SpaceWithinCastParentheses\">false</PropertyValue><PropertyValue name=\"NewlineInitListBrace\">0</PropertyValue><PropertyValue name=\"IndentCaseContents\">true</PropertyValue><PropertyValue name=\"DisablePreLoadNavigateToCache\">false</PropertyValue><PropertyValue name=\"AssignInsteadOfEqualityErrorTagType\">2</PropertyValue><PropertyValue name=\"ContinueCommentsOnEnter\">true</PropertyValue><PropertyValue name=\"MaximumErrorFixErrors\">50</PropertyValue><PropertyValue name=\"SortIncludesPriorityMatchingHeader\">true</PropertyValue><PropertyValue name=\"IncludeCleanupStrictMode\">false</PropertyValue><PropertyValue name=\"CreateMemberFunctionErrorTagType\">0</PropertyValue><PropertyValue name=\"EnterOutliningModeWhenFilesOpen\">true</PropertyValue><PropertyValue name=\"AutoTuneMaximumFindReferencesThreads\">true</PropertyValue><PropertyValue name=\"SpaceAroundConditionalOperator\">2</PropertyValue><PropertyValue name=\"PointerAlignment\">1</PropertyValue><PropertyValue name=\"SpaceWithinInitListBraces\">true</PropertyValue><PropertyValue name=\"SpaceBeforeLambdaParenthesis\">false</PropertyValue><PropertyValue name=\"NewlineScopeBrace\">false</PropertyValue><PropertyValue name=\"NewlineEmptyFunctionCloseBrace\">false</PropertyValue><PropertyValue name=\"DefaultFormattingStyle\">0</PropertyValue><PropertyValue name=\"EnableExpandPrecedence\">false</PropertyValue><PropertyValue name=\"DisableExperimentalCodeAnalysisFixits\">0</PropertyValue><PropertyValue name=\"InlineHintsHideAutoKeyword\">false</PropertyValue><PropertyValue name=\"ColorScheme\">1</PropertyValue><PropertyValue name=\"EnableSwitchToSlnInfoBar\">true</PropertyValue><PropertyValue name=\"AddSemicolonForClassTypes\">true</PropertyValue><PropertyValue name=\"DisableReferenceHighlighting\">true</PropertyValue><PropertyValue name=\"DoNotWarnIfFallbackLocationUsed\">false</PropertyValue><PropertyValue name=\"DisplayExternalSkippedRegions\">false</PropertyValue><PropertyValue name=\"DisableDatabaseImplicitAutoCleanup\">false</PropertyValue><PropertyValue name=\"EnableTemplateIntelliSense\">true</PropertyValue><PropertyValue name=\"DisableSharedIntelliSense\">false</PropertyValue><PropertyValue name=\"MemberListCommitCharacters\">{}[]().,:;+-*/%&amp;|^!=&lt;&gt;?@#\\</PropertyValue><PropertyValue name=\"PreserveBlock\">1</PropertyValue><PropertyValue name=\"SpaceBeforeConstructorColon\">true</PropertyValue><PropertyValue name=\"SpaceBeforeBlockBrace\">true</PropertyValue><PropertyValue name=\"NewlineKeywordWhile\">false</PropertyValue><PropertyValue name=\"NewlineLambdaBrace\">0</PropertyValue><PropertyValue name=\"HideExperimentalAd\">false</PropertyValue><PropertyValue name=\"EnableProjectCaching\">false</PropertyValue><PropertyValue name=\"InlineHintsShowNonConstRefIndicator\">true</PropertyValue><PropertyValue name=\"EnableSingleFileISense\">true</PropertyValue><PropertyValue name=\"RenameRenameStrings\">false</PropertyValue><PropertyValue name=\"DisableInactiveCodeOpacity\">false</PropertyValue><PropertyValue name=\"DisableDatabaseUpdates\">false</PropertyValue><PropertyValue name=\"UseForwardSlashForIncludeAutoComplete\">true</PropertyValue><PropertyValue name=\"AutoTuneMaximumCachedTranslationUnits\">true</PropertyValue><PropertyValue name=\"DisableIntelliSenseUpdating\">false</PropertyValue><PropertyValue name=\"HighlightMatchingTokens\">true</PropertyValue><PropertyValue name=\"SpaceBeforeFunctionParenthesis\">false</PropertyValue><PropertyValue name=\"NewlineNamespaceBrace\">0</PropertyValue><PropertyValue name=\"EnableClangFormatSupport\">true</PropertyValue><PropertyValue name=\"AutoFormatOnSemicolon\">true</PropertyValue><PropertyValue name=\"UserHasChangedBGAnalysisSetting\">false</PropertyValue><PropertyValue name=\"MaximumErrorFixThreads\">2</PropertyValue><PropertyValue name=\"CompleteSlashStar\">true</PropertyValue><PropertyValue name=\"FallbackLocation\">0</PropertyValue><PropertyValue name=\"UseWebSearchOnQIHelpLink\">true</PropertyValue><PropertyValue name=\"DisableErrorReporting\">false</PropertyValue><PropertyValue name=\"UseUnrealEngineMacroFormatting\">true</PropertyValue><PropertyValue name=\"NewlineControlBlockBrace\">0</PropertyValue><PropertyValue name=\"IndentNamespaceContents\">true</PropertyValue><PropertyValue name=\"IndentLambdaBraces\">true</PropertyValue><PropertyValue name=\"MemberListDotToArrow\">false</PropertyValue><PropertyValue name=\"IntellisenseOptions\">0</PropertyValue><PropertyValue name=\"EnablePoirotOutput\">true</PropertyValue><PropertyValue name=\"PoirotMaxDegreeOfParallelism\">2</PropertyValue><PropertyValue name=\"TaskScopeHideImplicitFile\">false</PropertyValue><PropertyValue name=\"ReferencesHideFailedResults\">false</PropertyValue><PropertyValue name=\"SuppressCMakeVcxprojNotificationInfoBar\">false</PropertyValue><PropertyValue name=\"SortIncludesKeepGroups\">true</PropertyValue><PropertyValue name=\"SortIncludesPriorityAngleBrackets\">1</PropertyValue><PropertyValue name=\"CreateDeclDefnErrorTagType\">2</PropertyValue><PropertyValue name=\"FormatAfterAutoSurround\">true</PropertyValue><PropertyValue name=\"AutoSurroundMode\">0</PropertyValue><PropertyValue name=\"SuppressBrowsingInfoBar\">false</PropertyValue><PropertyValue name=\"DisableBrowsingUpToDateCheck\">true</PropertyValue><PropertyValue name=\"DisableDatabaseImplicitFiles\">false</PropertyValue><PropertyValue name=\"AutoPchCacheQuota\">5120</PropertyValue><PropertyValue name=\"SpaceBeforeComma\">false</PropertyValue><PropertyValue name=\"IndentAccessSpecifiers\">false</PropertyValue><PropertyValue name=\"UseCustomClangFormatExe\">false</PropertyValue><PropertyValue name=\"OnlineHelpSearchProvider\"/><PropertyValue name=\"DisableCodeAnalysisInBackground\">false</PropertyValue><PropertyValue name=\"InlineHintsEnabled\">true</PropertyValue><PropertyValue name=\"EnableCodeAnalysisLogging\">false</PropertyValue><PropertyValue name=\"EnableLinter_1\">true</PropertyValue><PropertyValue name=\"SuspendNewSingleFileISenseDuringDebugging\">false</PropertyValue><PropertyValue name=\"IncludeCleanupMode\">0</PropertyValue><PropertyValue name=\"RenameRenameUnconfirmed\">false</PropertyValue><PropertyValue name=\"DisableReferencesResolving\">false</PropertyValue><PropertyValue name=\"HideExternalDependenciesFolders\">false</PropertyValue><PropertyValue name=\"DisableExternalDependenciesFolders\">false</PropertyValue><PropertyValue name=\"SmartMemberListCommitOnEnter\">false</PropertyValue><PropertyValue name=\"DisableIntelliSense\">false</PropertyValue><PropertyValue name=\"SpaceAroundAssignmentOperator\">2</PropertyValue><PropertyValue name=\"ClangFormatExePath\">0</PropertyValue><PropertyValue name=\"DontShowClangFormatFileFound\">false</PropertyValue><PropertyValue name=\"MemberListFilterHeuristic\">false</PropertyValue><PropertyValue name=\"DisableVCCodeAnalysisUX\">false</PropertyValue><PropertyValue name=\"InlineHintsShowParameterHints\">true</PropertyValue><PropertyValue name=\"UninitializedLocalVariableErrorTagType\">1</PropertyValue><PropertyValue name=\"IncludeHintsSuppressGoogleTest\">false</PropertyValue><PropertyValue name=\"ShowSingleFileISenseErrorsInTaskList\">false</PropertyValue><PropertyValue name=\"AutomaticOutliningOfStatementBlocks\">true</PropertyValue><PropertyValue name=\"EnableQIHelpLink\">true</PropertyValue><PropertyValue name=\"MemberListFilterInaccessible\">true</PropertyValue><PropertyValue name=\"UseTabToCommitInAggressiveMemberList\">true</PropertyValue><PropertyValue name=\"RemoveSpaceBeforeSemicolon\">true</PropertyValue><PropertyValue name=\"SpaceBetweenEmptyLambdaBrackets\">false</PropertyValue><PropertyValue name=\"SpaceBeforeControlBlockParenthesis\">true</PropertyValue><PropertyValue name=\"SpaceBetweenEmptyFunctionParentheses\">false</PropertyValue><PropertyValue name=\"PreserveParameterIndentation\">false</PropertyValue><PropertyValue name=\"PoirotCodeAnalysisErrorTagType\">2</PropertyValue><PropertyValue name=\"EnableErrorFixSuggestions\">true</PropertyValue><PropertyValue name=\"MacroToConstexprErrorTagType\">1</PropertyValue><PropertyValue name=\"SortIncludesPriorityCaseSensitive\">false</PropertyValue><PropertyValue name=\"RenameRenameComments\">false</PropertyValue><PropertyValue name=\"LoggingLevel\">5</PropertyValue><PropertyValue name=\"SpaceBeforeEmptyBracket\">false</PropertyValue><PropertyValue name=\"SpaceWithinExpressionParentheses\">false</PropertyValue><PropertyValue name=\"AutoFormatOnPaste2\">1</PropertyValue><PropertyValue name=\"AutoFormatOnPaste\">false</PropertyValue><PropertyValue name=\"InlineHintsUseCtrlCtrlShortcut\">false</PropertyValue><PropertyValue name=\"PoirotAccountUniqueId\">0</PropertyValue><PropertyValue name=\"SuppressPoirotInfoBar\">false</PropertyValue><PropertyValue name=\"MakeMemberFunctionConstErrorTagType\">1</PropertyValue><PropertyValue name=\"SuppressUEPluginInfoBar\">false</PropertyValue><PropertyValue name=\"EnableSingleFileISenseSquiggles\">false</PropertyValue><PropertyValue name=\"DisableCreateDeclDefnScan\">false</PropertyValue><PropertyValue name=\"RenameRenameInactive\">false</PropertyValue><PropertyValue name=\"ColorizeInactiveBlocksDifferently\">true</PropertyValue><PropertyValue name=\"IntelliSenseProcessMemoryLimit\">4096</PropertyValue><PropertyValue name=\"MemberListDotToArrow_1\">true</PropertyValue><PropertyValue name=\"MemberListFilterMode\">3</PropertyValue><PropertyValue name=\"TrimSpaceUnaryOperator\">true</PropertyValue><PropertyValue name=\"TrimSpaceAroundScope\">true</PropertyValue><PropertyValue name=\"GroupBrackets\">true</PropertyValue><PropertyValue name=\"SpaceBetweenEmptyBrackets\">false</PropertyValue><PropertyValue name=\"SpaceWithinControlBlockParentheses\">false</PropertyValue><PropertyValue name=\"NewlineTypeBrace\">0</PropertyValue><PropertyValue name=\"UseQuotesForIncludes\">false</PropertyValue><PropertyValue name=\"EnableIncludeCleanup_Exp1\">false</PropertyValue><PropertyValue name=\"SkippedRegionErrorTagType\">1</PropertyValue><PropertyValue name=\"IncludeCleanupSortAfterEdits\">false</PropertyValue><PropertyValue name=\"RenameSkipPreviewIfConfirmed\">false</PropertyValue><PropertyValue name=\"AlwaysUseFallbackLocation\">false</PropertyValue><PropertyValue name=\"RescanSolutionInterval\">60</PropertyValue><PropertyValue name=\"DisableDatabaseAutoUpdates\">false</PropertyValue><PropertyValue name=\"IncludeStyle\">0</PropertyValue><PropertyValue name=\"EnableQISizeAlignment\">true</PropertyValue><PropertyValue name=\"DisableSquiggles\">false</PropertyValue><PropertyValue name=\"EnableQuickInfoToolTips\">true</PropertyValue><PropertyValue name=\"SpaceAfterCastParenthesis\">false</PropertyValue><PropertyValue name=\"EnablePoirotCodeAnalysis\">false</PropertyValue><PropertyValue name=\"EnableExtractFunction\">false</PropertyValue><PropertyValue name=\"EnableExpandScopes\">false</PropertyValue><PropertyValue name=\"EnableChangeSignature\">false</PropertyValue><PropertyValue name=\"DisableConvertToWhileLoop\">false</PropertyValue><PropertyValue name=\"ShowProblemDetailsOnNavigate\">true</PropertyValue><PropertyValue name=\"InlineHintsShowDeclHints\">true</PropertyValue><PropertyValue name=\"TaskScopeHideUnknownFile\">false</PropertyValue><PropertyValue name=\"DisableCodeAnalysisSquiggles\">false</PropertyValue><PropertyValue name=\"IncludeHintsSuppress\">false</PropertyValue><PropertyValue name=\"CompleteParensInRawString\">true</PropertyValue><PropertyValue name=\"InactiveCodeOpacityPercent\">55</PropertyValue><PropertyValue name=\"MemberListItemFilterForEnums\">true</PropertyValue><PropertyValue name=\"EnableQISizeAlignmentAllSymbols\">false</PropertyValue><PropertyValue name=\"InactivePlatformIntelliSenseLimit\">1</PropertyValue><PropertyValue name=\"DisableModules\">false</PropertyValue><PropertyValue name=\"DisableSemanticColoring\">false</PropertyValue><PropertyValue name=\"IndentBraces\">false</PropertyValue><PropertyValue name=\"PreserveCommentIndentation\">true</PropertyValue><PropertyValue name=\"IndentPreprocessor\">0</PropertyValue><PropertyValue name=\"IndentCaseBraces\">false</PropertyValue><PropertyValue name=\"AlignParameters\">false</PropertyValue><PropertyValue name=\"AutoFormatOnBraceCompletion\">true</PropertyValue><PropertyValue name=\"AutoFormatOnClosingBrace\">true</PropertyValue><PropertyValue name=\"InlineHintsHideIfParamNameFoundInArg\">true</PropertyValue><PropertyValue name=\"EnablePoirotCodeAnalysis_1\">true</PropertyValue><PropertyValue name=\"NamingConventionErrorTagType\">1</PropertyValue><PropertyValue name=\"GeneratedDocumentationType\">1</PropertyValue><PropertyValue name=\"ReferencesHideInStringResults\">true</PropertyValue><PropertyValue name=\"ReferencesHidePPIf0Results\">true</PropertyValue><PropertyValue name=\"DisableCodeAnalysisInBackground_1\">false</PropertyValue><PropertyValue name=\"DisplayIncludesData\">false</PropertyValue><PropertyValue name=\"RefactoringNavigationAction\">2</PropertyValue><PropertyValue name=\"SpaceBeforeInheritanceColon\">true</PropertyValue><PropertyValue name=\"NewlineKeywordElse\">false</PropertyValue><PropertyValue name=\"NewlineKeywordCatch\">false</PropertyValue><PropertyValue name=\"AutoIndentOnTab\">false</PropertyValue><PropertyValue name=\"IndentCaseLabels\">true</PropertyValue><PropertyValue name=\"ClangFormatExecution\">1</PropertyValue><PropertyValue name=\"RenameSearchComments\">false</PropertyValue><PropertyValue name=\"EnableSQLiteStoreEngine\">false</PropertyValue><PropertyValue name=\"InlineHintsRenderHints\">false</PropertyValue><PropertyValue name=\"PoirotAccountProviderId\">0</PropertyValue><PropertyValue name=\"LogicalBitwiseMismatchErrorTagType\">2</PropertyValue><PropertyValue name=\"ReferencesHideDisconfirmedResults\">true</PropertyValue><PropertyValue name=\"IncludeHintsSuppressBoostTest\">false</PropertyValue><PropertyValue name=\"AutomaticOutliningOfPragmaRegions\">true</PropertyValue><PropertyValue name=\"EnableSquiggleHelpLink\">true</PropertyValue><PropertyValue name=\"DisableAutoPch\">false</PropertyValue><PropertyValue name=\"DisableMemberListKeywords\">false</PropertyValue><PropertyValue name=\"Enable64bitIntelliSense\">false</PropertyValue><PropertyValue name=\"EnumerateCommentTasks\">true</PropertyValue><PropertyValue name=\"SpaceWithinLambdaBrackets\">false</PropertyValue><PropertyValue name=\"SpaceWithinBrackets\">false</PropertyValue><PropertyValue name=\"RenameShowPreview\">false</PropertyValue><PropertyValue name=\"RenameSearchStrings\">false</PropertyValue><PropertyValue name=\"EnableIncludeCleanup_Exp\">false</PropertyValue><PropertyValue name=\"DisableIntelliSenseErrorsInErrorList\">false</PropertyValue><PropertyValue name=\"CreateDeclDefNavigation_Exp\">0</PropertyValue><PropertyValue name=\"ComparisonBitwisePrecedenceErrorTagType\">2</PropertyValue><PropertyValue name=\"AccidentalCopyErrorTagType\">1</PropertyValue><PropertyValue name=\"ArithmeticOverflowErrorTagType\">1</PropertyValue><PropertyValue name=\"RescanSolution\">false</PropertyValue><PropertyValue name=\"DisableDatabase\">false</PropertyValue><PropertyValue name=\"UseAggressiveMemberListForAutoMemberList\">false</PropertyValue><PropertyValue name=\"MemberListCommitAggressive\">false</PropertyValue><PropertyValue name=\"DisableMemberListExpansions\">false</PropertyValue><PropertyValue name=\"LoggingFilter\">0</PropertyValue><PropertyValue name=\"SpaceAfterComma\">true</PropertyValue><PropertyValue name=\"SpaceBetweenEmptyBraces\">false</PropertyValue><PropertyValue name=\"IndentGotoLabels\">1</PropertyValue><PropertyValue name=\"EnableIncludeCleanup\">false</PropertyValue><PropertyValue name=\"DisableConvertToForLoop\">false</PropertyValue><PropertyValue name=\"PoirotProcessingPriority\">16384</PropertyValue><PropertyValue name=\"TaskScopeHideExternalFile\">true</PropertyValue><PropertyValue name=\"FloatTruncatedErrorTagType\">1</PropertyValue><PropertyValue name=\"OnlineHelpSearchProvider_1\">https://www.bing.com/search?q={0}</PropertyValue><PropertyValue name=\"IncludeCleanupAddMissingErrorTagType\">1</PropertyValue><PropertyValue name=\"MakeGlobalFunctionStaticErrorTagType\">1</PropertyValue><PropertyValue name=\"DisableIncludeAutoComplete\">false</PropertyValue><PropertyValue name=\"EnableLogging\">false</PropertyValue><PropertyValue name=\"SpaceBeforeBracket\">false</PropertyValue><PropertyValue name=\"SpaceWithinFunctionParentheses\">false</PropertyValue><PropertyValue name=\"NewlineEmptyTypeCloseBrace\">false</PropertyValue><PropertyValue name=\"IndentationReference\">2</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"CSS\" RegisteredName=\"CSS\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">true</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"CSharp\" RegisteredName=\"CSharp\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">true</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"CSharp-Specific\" RegisteredName=\"CSharp-Specific\" PackageName=\"CSharpPackage\"><PropertyValue name=\"Style_RemoveUnnecessarySuppressionExclusions\"/><PropertyValue name=\"Style_PreferExpressionBodiedConstructors\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferPatternMatching\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"NavigateToSourceLinkAndEmbeddedSources\">1</PropertyValue><PropertyValue name=\"CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened\">0</PropertyValue><PropertyValue name=\"Style_UseVarWhenDeclaringLocals\">0</PropertyValue><PropertyValue name=\"Formatting_TriggerOnPaste\">0</PropertyValue><PropertyValue name=\"Space_AroundBinaryOperator\">0</PropertyValue><PropertyValue name=\"NewLines_QueryExpression_EachClause\">1</PropertyValue><PropertyValue name=\"Style_AllowMultipleBlankLines\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_UseImplicitTypeWhereApparent\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferInferredTupleNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_QualifyMethodAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration_CodeStyle\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"SkipAnalyzersForImplicitlyTriggeredBuilds\">1</PropertyValue><PropertyValue name=\"OfferRemoveUnusedReferences\">1</PropertyValue><PropertyValue name=\"CollapseMetadataSignatureFilesWhenFirstOpened\">1</PropertyValue><PropertyValue name=\"AutoInsertAsteriskForNewLinesOfBlockComments\">1</PropertyValue><PropertyValue name=\"Fading_FadeOutUnusedImports\">1</PropertyValue><PropertyValue name=\"ExtractMethod_DoNotPutOutOrRefOnStruct\">1</PropertyValue><PropertyValue name=\"ShowItemsFromUnimportedNamespaces\">-1</PropertyValue><PropertyValue name=\"Style_PreferCompoundAssignment\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedAccessors\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedOperators\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferCoalesceExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"HighlightReferences\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_Method\">1</PropertyValue><PropertyValue name=\"AddImport_SuggestForTypesInReferenceAssemblies\">0</PropertyValue><PropertyValue name=\"Style_PreferredModifierOrder\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"String\" Value=\"public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_RequireAccessibilityModifiers\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Int32\" Value=\"2\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferPatternMatchingOverIsWithCastCheck\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"EnableHighlightRelatedKeywords\">1</PropertyValue><PropertyValue name=\"Indent_CaseLabels\">1</PropertyValue><PropertyValue name=\"ExtractMethod_AllowBestEffort\">1</PropertyValue><PropertyValue name=\"InsertNewlineOnEnterWithWholeWord\">0</PropertyValue><PropertyValue name=\"Formatting_TriggerOnBlockCompletion\">0</PropertyValue><PropertyValue name=\"Style_UnusedValueAssignment\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Int32\" Value=\"2\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferReadOnlyStruct\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferInlinedVariableDeclaration\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"NavigateToDecompiledSources\">1</PropertyValue><PropertyValue name=\"Space_WithinOtherParentheses\">0</PropertyValue><PropertyValue name=\"NewLines_Braces_ControlFlow\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_AnonymousMethod\">1</PropertyValue><PropertyValue name=\"Fading_FadeOutUnreachableCode\">1</PropertyValue><PropertyValue name=\"HighlightMatchingPortionsOfCompletionListItems\">1</PropertyValue><PropertyValue name=\"BringUpOnIdentifier\">1</PropertyValue><PropertyValue name=\"Style_PreferConditionalExpressionOverReturn\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExplicitTupleNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"AutomaticallyCompleteStatementOnSemicolon\">1</PropertyValue><PropertyValue name=\"AddImportsOnPaste\">1</PropertyValue><PropertyValue name=\"CollapseRegionsWhenFirstOpened\">1</PropertyValue><PropertyValue name=\"DisplayLineSeparators\">0</PropertyValue><PropertyValue name=\"Wrapping_KeepStatementsOnSingleLine\">1</PropertyValue><PropertyValue name=\"Space_AfterBasesColon\">1</PropertyValue><PropertyValue name=\"Indent_Braces\">0</PropertyValue><PropertyValue name=\"EnterKeyBehavior\">0</PropertyValue><PropertyValue name=\"AddImport_SuggestForTypesInNuGetPackages\">0</PropertyValue><PropertyValue name=\"Style_PreferTopLevelStatements\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferIndexOperator\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferDeconstructedVariableDeclaration\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferPatternMatchingOverAsWithNullCheck\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferConditionalDelegateCall\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_UseImplicitTypeForIntrinsicTypes\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"WarnOnBuildErrors\">0</PropertyValue><PropertyValue name=\"SortUsings_PlaceSystemFirst\">0</PropertyValue><PropertyValue name=\"Wrapping_IgnoreSpacesAroundVariableDeclaration\">0</PropertyValue><PropertyValue name=\"Space_WithinMethodCallParentheses\">0</PropertyValue><PropertyValue name=\"Space_AfterCast\">0</PropertyValue><PropertyValue name=\"Style_ImplicitObjectCreationWhenTypeIsApparent\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferLocalOverAnonymousFunction\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferNamespaceAndFolderMatchStructure\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSimplifiedInterpolation\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_QualifyEventAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"AutomaticallyFixStringContentsOnPaste\">1</PropertyValue><PropertyValue name=\"ShowSnippets\">0</PropertyValue><PropertyValue name=\"ShowKeywords\">0</PropertyValue><PropertyValue name=\"Style_NamingPreferences\">&lt;NamingPreferencesInfo SerializationVersion=\"5\"&gt;\n  &lt;SymbolSpecifications&gt;\n    &lt;SymbolSpecification ID=\"5c545a62-b14d-460a-88d8-e936c0a39316\" Name=\"Class\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Class&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"23d856b4-5089-4405-83ce-749aada99153\" Name=\"Interface\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Interface&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"d1796e78-ff66-463f-8576-eb46416060c0\" Name=\"Struct\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Struct&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"d8af8dc6-1ade-441d-9947-8946922e198a\" Name=\"Enum\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Enum&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"408a3347-b908-4b54-a954-1355e64c1de3\" Name=\"Delegate\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Delegate&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"830657f6-e7e5-4830-b328-f109d3b6c165\" Name=\"Event\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Event&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"390caed4-f0a9-42bb-adbb-b44c4a302a22\" Name=\"Method\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;MethodKind&gt;Ordinary&lt;/MethodKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"af410767-f189-47c6-b140-aeccf1ff242e\" Name=\"Private Method\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;MethodKind&gt;Ordinary&lt;/MethodKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"8076757e-6a4a-47f1-9b4b-ae8a3284e987\" Name=\"Abstract Method\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;MethodKind&gt;Ordinary&lt;/MethodKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList&gt;\n        &lt;ModifierKind&gt;IsAbstract&lt;/ModifierKind&gt;\n      &lt;/RequiredModifierList&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"16133061-a8e7-4392-92c3-1d93cd54c218\" Name=\"Static Method\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;MethodKind&gt;Ordinary&lt;/MethodKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList&gt;\n        &lt;ModifierKind&gt;IsStatic&lt;/ModifierKind&gt;\n      &lt;/RequiredModifierList&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"da6a2919-5aa6-4ad1-a24d-576776ed3974\" Name=\"Property\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Property&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"b24a91ce-3501-4799-b6df-baf044156c83\" Name=\"Public or Protected Field\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Field&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"70af42cb-1741-4027-969c-9edc4877d965\" Name=\"Static Field\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Field&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList&gt;\n        &lt;ModifierKind&gt;IsStatic&lt;/ModifierKind&gt;\n      &lt;/RequiredModifierList&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"10790aa6-0a0b-432d-a52d-d252ca92302b\" Name=\"Private or Internal Field\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Field&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"ac995be4-88de-4771-9dcc-a456a7c02d89\" Name=\"Private or Internal Static Field\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Field&lt;/SymbolKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList&gt;\n        &lt;ModifierKind&gt;IsStatic&lt;/ModifierKind&gt;\n      &lt;/RequiredModifierList&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"2c07f5bf-bc81-4c2b-82b4-ae9b3ffd0ba4\" Name=\"Types\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;TypeKind&gt;Class&lt;/TypeKind&gt;\n        &lt;TypeKind&gt;Struct&lt;/TypeKind&gt;\n        &lt;TypeKind&gt;Interface&lt;/TypeKind&gt;\n        &lt;TypeKind&gt;Enum&lt;/TypeKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n    &lt;SymbolSpecification ID=\"5f3ddba1-279f-486c-801e-5c097c36dd85\" Name=\"Non-Field Members\"&gt;\n      &lt;ApplicableSymbolKindList&gt;\n        &lt;SymbolKind&gt;Property&lt;/SymbolKind&gt;\n        &lt;SymbolKind&gt;Event&lt;/SymbolKind&gt;\n        &lt;MethodKind&gt;Ordinary&lt;/MethodKind&gt;\n      &lt;/ApplicableSymbolKindList&gt;\n      &lt;ApplicableAccessibilityList&gt;\n        &lt;AccessibilityKind&gt;Public&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Internal&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Private&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;Protected&lt;/AccessibilityKind&gt;\n        &lt;AccessibilityKind&gt;ProtectedOrInternal&lt;/AccessibilityKind&gt;\n      &lt;/ApplicableAccessibilityList&gt;\n      &lt;RequiredModifierList /&gt;\n    &lt;/SymbolSpecification&gt;\n  &lt;/SymbolSpecifications&gt;\n  &lt;NamingStyles&gt;\n    &lt;NamingStyle ID=\"87e7c501-9948-4b53-b1eb-a6cbe918feee\" Name=\"Pascal Case\" Prefix=\"\" Suffix=\"\" WordSeparator=\"\" CapitalizationScheme=\"PascalCase\" /&gt;\n    &lt;NamingStyle ID=\"1ecc5eb6-b5fc-49a5-a9f1-a980f3e48c92\" Name=\"Begins with I\" Prefix=\"I\" Suffix=\"\" WordSeparator=\"\" CapitalizationScheme=\"PascalCase\" /&gt;\n  &lt;/NamingStyles&gt;\n  &lt;NamingRules&gt;\n    &lt;SerializableNamingRule SymbolSpecificationID=\"23d856b4-5089-4405-83ce-749aada99153\" NamingStyleID=\"1ecc5eb6-b5fc-49a5-a9f1-a980f3e48c92\" EnforcementLevel=\"Info\" /&gt;\n    &lt;SerializableNamingRule SymbolSpecificationID=\"2c07f5bf-bc81-4c2b-82b4-ae9b3ffd0ba4\" NamingStyleID=\"87e7c501-9948-4b53-b1eb-a6cbe918feee\" EnforcementLevel=\"Info\" /&gt;\n    &lt;SerializableNamingRule SymbolSpecificationID=\"5f3ddba1-279f-486c-801e-5c097c36dd85\" NamingStyleID=\"87e7c501-9948-4b53-b1eb-a6cbe918feee\" EnforcementLevel=\"Info\" /&gt;\n  &lt;/NamingRules&gt;\n&lt;/NamingPreferencesInfo&gt;</PropertyValue><PropertyValue name=\"SortUsings_SeparateImportDirectiveGroups\">0</PropertyValue><PropertyValue name=\"Style_NamespaceDeclarations\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferredUsingDirectivePlacement\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferNotPattern\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferConditionalExpressionOverAssignment\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedLambdas\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"CSharpClosedFileDiagnostics\">0</PropertyValue><PropertyValue name=\"ClosedFileDiagnostics\">0</PropertyValue><PropertyValue name=\"AutoFormattingOnTyping\">0</PropertyValue><PropertyValue name=\"Formatting_TriggerOnStatementCompletion\">0</PropertyValue><PropertyValue name=\"Space_WithinExpressionParentheses\">0</PropertyValue><PropertyValue name=\"Space_InControlFlowConstruct\">1</PropertyValue><PropertyValue name=\"NewLines_Keywords_Catch\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_ObjectInitializer\">1</PropertyValue><PropertyValue name=\"NewLines_AnonymousTypeInitializer_EachMember\">1</PropertyValue><PropertyValue name=\"Style_AllowBlankLineAfterColonInConstructorInitializer\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_UnusedValueExpressionStatement\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Int32\" Value=\"2\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_AllowStatementImmediatelyAfterBlock\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferAutoProperties\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferBraces\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedLocalFunctions\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess_CodeStyle\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Space_BetweenEmptyMethodDeclarationParentheses\">0</PropertyValue><PropertyValue name=\"NewLines_Keywords_Finally\">1</PropertyValue><PropertyValue name=\"Indent_UnindentLabels\">1</PropertyValue><PropertyValue name=\"Style_AllowBlankLineAfterTokenInConditionalExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_AllowBlankLinesBetweenConsecutiveBraces\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferReadonly\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferNullPropagation\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"NewLines_ObjectInitializer_EachMember\">1</PropertyValue><PropertyValue name=\"NewLines_Keywords_Else\">1</PropertyValue><PropertyValue name=\"Style_AllowEmbeddedStatementsOnSameLine\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSimpleUsingStatement\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSimpleDefaultExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSystemHashCode\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedIndexers\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferInferredAnonymousTypeMemberNames\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferObjectInitializer\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Space_WithinMethodDeclarationParentheses\">0</PropertyValue><PropertyValue name=\"Space_BetweenEmptyMethodCallParentheses\">0</PropertyValue><PropertyValue name=\"Space_BeforeSemicolonsInForStatement\">0</PropertyValue><PropertyValue name=\"Space_BeforeComma\">0</PropertyValue><PropertyValue name=\"Space_AfterMethodCallName\">0</PropertyValue><PropertyValue name=\"Space_AfterComma\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_Property\">1</PropertyValue><PropertyValue name=\"Style_PreferIsNullCheckOverReferenceEqualityMethod\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferCollectionInitializer\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"AlwaysUseDefaultSymbolServers\">1</PropertyValue><PropertyValue name=\"RenameTrackingPreview\">1</PropertyValue><PropertyValue name=\"Wrapping_IgnoreSpacesAroundBinaryOperators\">0</PropertyValue><PropertyValue name=\"Space_BeforeBasesColon\">1</PropertyValue><PropertyValue name=\"Space_AfterMethodDeclarationName\">0</PropertyValue><PropertyValue name=\"Space_AfterDot\">0</PropertyValue><PropertyValue name=\"AutoComment\">1</PropertyValue><PropertyValue name=\"EnableArgumentCompletionSnippets\">-1</PropertyValue><PropertyValue name=\"Style_AllowBlankLineAfterTokenInArrowExpressionClause\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSimplifiedBooleanExpressions\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedProperties\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferSwitchExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"EnterOutliningModeOnOpen\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_Type\">1</PropertyValue><PropertyValue name=\"TriggerInArgumentLists\">1</PropertyValue><PropertyValue name=\"SnippetsBehavior\">0</PropertyValue><PropertyValue name=\"Style_PreferExpressionBodiedMethods\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferThrowExpression\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_QualifyPropertyAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Space_WithinSquares\">0</PropertyValue><PropertyValue name=\"NewLines_Braces_Accessor\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_LambdaExpressionBody\">1</PropertyValue><PropertyValue name=\"Style_PreferTupleSwap\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferStaticLocalFunction\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferRangeOperator\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferNullCheckOverTypeCheck\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Info\" /&gt;</PropertyValue><PropertyValue name=\"Style_UseImplicitTypeWherePossible\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_PreferMethodGroupConversion\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"true\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Style_QualifyFieldAccess\">&lt;CodeStyleOption SerializationVersion=\"1\" Type=\"Boolean\" Value=\"false\" DiagnosticSeverity=\"Hidden\" /&gt;</PropertyValue><PropertyValue name=\"Space_WithinCastParentheses\">0</PropertyValue><PropertyValue name=\"Space_AfterSemicolonsInForStatement\">1</PropertyValue><PropertyValue name=\"NewLines_Braces_AnonymousTypeInitializer\">1</PropertyValue><PropertyValue name=\"Indent_FlushLabelsLeft\">1</PropertyValue><PropertyValue name=\"Indent_CaseContents\">1</PropertyValue><PropertyValue name=\"RenameSmartTagEnabled\">1</PropertyValue><PropertyValue name=\"Refactoring_Verification_Enabled\">0</PropertyValue><PropertyValue name=\"CollapseImportsWhenFirstOpened\">0</PropertyValue><PropertyValue name=\"Wrapping_PreserveSingleLine\">1</PropertyValue><PropertyValue name=\"Space_BetweenEmptySquares\">0</PropertyValue><PropertyValue name=\"Space_BeforeOpenSquare\">0</PropertyValue><PropertyValue name=\"Space_BeforeDot\">0</PropertyValue><PropertyValue name=\"Indent_CaseContentsWhenBlock\">1</PropertyValue><PropertyValue name=\"Indent_BlockContents\">1</PropertyValue><PropertyValue name=\"ShowCompletionItemFilters\">1</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"HTML\" RegisteredName=\"HTML\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"JSON\" RegisteredName=\"JSON\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"LESS\" RegisteredName=\"LESS\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">true</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"PlainText\" RegisteredName=\"PlainText\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">true</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"Rest\" RegisteredName=\"Rest\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">true</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">false</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">false</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">false</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"SCSS\" RegisteredName=\"SCSS\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"T-SQL90\" RegisteredName=\"T-SQL90\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">false</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"WebForms\" RegisteredName=\"WebForms\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">true</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"WebForms Specific\" RegisteredName=\"WebForms Specific\" PackageName=\"Visual Studio HTM Editor Package\"><PropertyValue name=\"StyleApplicationOverlayColor\">16244681</PropertyValue><PropertyValue name=\"LayoutResizeOverlayColor\">0</PropertyValue><PropertyValue name=\"LayoutVisibleBorderColor\">13027014</PropertyValue><PropertyValue name=\"LayoutUiActivatedBorderColor\">13003057</PropertyValue><PropertyValue name=\"LayoutOffsetColor\">16487691</PropertyValue><PropertyValue name=\"SpacerCellToolColor\">14413810</PropertyValue><PropertyValue name=\"ShowTagToolbar\">0</PropertyValue><PropertyValue name=\"RulerAndGridShowRuler\">0</PropertyValue><PropertyValue name=\"LayoutTableToolColor\">3575607</PropertyValue><PropertyValue name=\"HtmlIndent\">2</PropertyValue><PropertyValue name=\"DisableDesignView\">0</PropertyValue><PropertyValue name=\"MarkupValidationWarningLevel\">2</PropertyValue><PropertyValue name=\"ClientAttrCasing\">2</PropertyValue><PropertyValue name=\"AttrValueNotQuoted\">0</PropertyValue><PropertyValue name=\"CssPositionDesignMode\">2</PropertyValue><PropertyValue name=\"LayerBackgroundLabelColor\">15724527</PropertyValue><PropertyValue name=\"AutoThumbnailBorder\">1</PropertyValue><PropertyValue name=\"JsEditorTimeout\">50</PropertyValue><PropertyValue name=\"ManualDtp\">243</PropertyValue><PropertyValue name=\"StyleApplicationOverlayBackgroundColor\">15577469</PropertyValue><PropertyValue name=\"LayoutSiteSelectedBorderColor\">0</PropertyValue><PropertyValue name=\"CssUseHtmlForImageSize\">0</PropertyValue><PropertyValue name=\"CssBackgroundDesignMode\">2</PropertyValue><PropertyValue name=\"HtmlGifInterlaced\">0</PropertyValue><PropertyValue name=\"JsExternalTimeout\">2000</PropertyValue><PropertyValue name=\"AutoInsertCloseTag\">-1</PropertyValue><PropertyValue name=\"InsertAttrValueQuotesFormatting\">-1</PropertyValue><PropertyValue name=\"WebPartColor\">7257087</PropertyValue><PropertyValue name=\"TemplateColor\">12383999</PropertyValue><PropertyValue name=\"RulerAndGridShowGrid\">0</PropertyValue><PropertyValue name=\"ParentRegionColor\">128</PropertyValue><PropertyValue name=\"LargePictureFileType\">jpg</PropertyValue><PropertyValue name=\"FormatHTMLOnPaste\">0</PropertyValue><PropertyValue name=\"WrapTagLength\">80</PropertyValue><PropertyValue name=\"SmallPictureFileType\">gif</PropertyValue><PropertyValue name=\"HtmlMargin\">80</PropertyValue><PropertyValue name=\"JsFailsafeTimeout\">15000</PropertyValue><PropertyValue name=\"RequireAngleBracketForTagIntellisense\">-1</PropertyValue><PropertyValue name=\"RulerAndGridGridSpacing\">20</PropertyValue><PropertyValue name=\"EditorOptionFlags\">8388616</PropertyValue><PropertyValue name=\"JsMaxCacheFiles\">5</PropertyValue><PropertyValue name=\"IdleYield\">-1</PropertyValue><PropertyValue name=\"IdleParseDelay\">750</PropertyValue><PropertyValue name=\"InsertAttrValueQuotesTyping\">-1</PropertyValue><PropertyValue name=\"LayoutResizeOverlayBackgroundColor\">13172735</PropertyValue><PropertyValue name=\"LayoutPaddingOverlayColor\">16765887</PropertyValue><PropertyValue name=\"CssBorderDesignMode\">2</PropertyValue><PropertyValue name=\"RulerAndGridSnapToGrid\">0</PropertyValue><PropertyValue name=\"LayerLabelTextColor\">0</PropertyValue><PropertyValue name=\"HtmlDoctypeValidation\">-1</PropertyValue><PropertyValue name=\"LoadToolboxOnIdle\">-1</PropertyValue><PropertyValue name=\"ClientTagNotLowerCase\">0</PropertyValue><PropertyValue name=\"RestrictionsBOMFlag\">2147483647</PropertyValue><PropertyValue name=\"HtmlRightMarginRule\">2</PropertyValue><PropertyValue name=\"HtmlBreakInTags\">255</PropertyValue><PropertyValue name=\"AutoThumbnailSize\">100</PropertyValue><PropertyValue name=\"HTMLSortAttributes\">1</PropertyValue><PropertyValue name=\"LayoutMarginOverlayColor\">12895452</PropertyValue><PropertyValue name=\"CssPageDesignMode\">1</PropertyValue><PropertyValue name=\"RulerAndGridSnapDistance\">5</PropertyValue><PropertyValue name=\"RulerAndGridGridLine\">2</PropertyValue><PropertyValue name=\"MasterPageDesignOutlineColor\">16760311</PropertyValue><PropertyValue name=\"LayoutCellToolColor\">16750848</PropertyValue><PropertyValue name=\"AutoThumbnailBorderSize\">2</PropertyValue><PropertyValue name=\"MarkupValidationErrorLevel\">1</PropertyValue><PropertyValue name=\"VerticalSplitView\">0</PropertyValue><PropertyValue name=\"WrapTag\">0</PropertyValue><PropertyValue name=\"ServerAttrCasing\">3</PropertyValue><PropertyValue name=\"ServerTagCasing\">3</PropertyValue><PropertyValue name=\"TagNotWellFormed\">0</PropertyValue><PropertyValue name=\"CssUseShorthands\">1</PropertyValue><PropertyValue name=\"CssListDesignMode\">2</PropertyValue><PropertyValue name=\"MasterPageParentRegionColor\">8650752</PropertyValue><PropertyValue name=\"HtmlJpegCompressions\">0</PropertyValue><PropertyValue name=\"IdleOutlineDelay\">1800</PropertyValue><PropertyValue name=\"InCSS\">-1</PropertyValue><PropertyValue name=\"LayoutMarginOverlayBackgroundColor\">14145511</PropertyValue><PropertyValue name=\"CssMarginsDesignMode\">2</PropertyValue><PropertyValue name=\"LayoutPaddingOverlayBackgroundColor\">16768975</PropertyValue><PropertyValue name=\"HtmlGifTransparent\">0</PropertyValue><PropertyValue name=\"EditableColor\">7257087</PropertyValue><PropertyValue name=\"AutoThumbnailChisel\">0</PropertyValue><PropertyValue name=\"JsJQueryTemplateTypeNames\">text/html;text/x-jquery-tmpl;text/template;text/x-handlebars;text/x-handlebars-template;text/x-jsrender</PropertyValue><PropertyValue name=\"MasterPageContentRegionColor\">16739258</PropertyValue><PropertyValue name=\"LayerHandleBackgroundColor\">16777215</PropertyValue><PropertyValue name=\"LayerHandleColor\">0</PropertyValue><PropertyValue name=\"HtmlJpegQuality\">90</PropertyValue><PropertyValue name=\"TargetFriendlyName\">HTML5</PropertyValue><PropertyValue name=\"ValidateDeprecated\">0</PropertyValue><PropertyValue name=\"ShowAspNonVisualElements\">1</PropertyValue><PropertyValue name=\"CssReuseUserClasses\">0</PropertyValue><PropertyValue name=\"IdleSelectionSyncDelay\">500</PropertyValue><PropertyValue name=\"ShowErrors\">-1</PropertyValue><PropertyValue name=\"ClientTagCasing\">2</PropertyValue><PropertyValue name=\"RulerAndGridUnit\">0</PropertyValue><PropertyValue name=\"RulerAndGridGridColor\">11119017</PropertyValue><PropertyValue name=\"HtmlIndentTabs\">0</PropertyValue><PropertyValue name=\"StartPageView\">1</PropertyValue><PropertyValue name=\"CssIndentTabs\">0</PropertyValue><PropertyValue name=\"AutoThumbnailType\">0</PropertyValue><PropertyValue name=\"AutoFormAroundControls\">1</PropertyValue><PropertyValue name=\"ShowVisualAids\">1</PropertyValue><PropertyValue name=\"CssTextDesignMode\">2</PropertyValue><PropertyValue name=\"WebPartZoneColor\">16750848</PropertyValue><PropertyValue name=\"IdleMainLoopDelay\">1200</PropertyValue><PropertyValue name=\"OutlineHTMLOnFileOpen\">-1</PropertyValue></ToolsOptionsSubCategory><ToolsOptionsSubCategory name=\"XML\" RegisteredName=\"XML\" PackageName=\"Text Management Package\"><PropertyValue name=\"TabSize\">4</PropertyValue><PropertyValue name=\"ShowChanges\">true</PropertyValue><PropertyValue name=\"AutoListMembers\">true</PropertyValue><PropertyValue name=\"ShowPreview\">true</PropertyValue><PropertyValue name=\"ShowMarks\">true</PropertyValue><PropertyValue name=\"IndentStyle\">0</PropertyValue><PropertyValue name=\"ShowCaretPosition\">true</PropertyValue><PropertyValue name=\"HideAdvancedMembers\">true</PropertyValue><PropertyValue name=\"ShowNavigationBar\">false</PropertyValue><PropertyValue name=\"UseMapMode\">false</PropertyValue><PropertyValue name=\"VirtualSpace\">false</PropertyValue><PropertyValue name=\"ShowAnnotations\">true</PropertyValue><PropertyValue name=\"ShowVerticalScrollBar\">true</PropertyValue><PropertyValue name=\"InsertTabs\">false</PropertyValue><PropertyValue name=\"WordWrapGlyphs\">true</PropertyValue><PropertyValue name=\"EnableLeftClickForURLs\">true</PropertyValue><PropertyValue name=\"ShowErrors\">true</PropertyValue><PropertyValue name=\"OverviewWidth\">58</PropertyValue><PropertyValue name=\"ShowLineNumbers\">true</PropertyValue><PropertyValue name=\"WordWrap\">false</PropertyValue><PropertyValue name=\"IndentSize\">4</PropertyValue><PropertyValue name=\"BraceCompletion\">false</PropertyValue><PropertyValue name=\"ShowHorizontalScrollBar\">true</PropertyValue><PropertyValue name=\"CutCopyBlankLines\">true</PropertyValue><PropertyValue name=\"AutoListParams\">true</PropertyValue></ToolsOptionsSubCategory></ToolsOptionsCategory></ToolsOptions><Category name=\"Environment_Group\" RegisteredName=\"Environment_Group\"><Category name=\"Environment_Aliases\" Category=\"{AD334E74-368A-4c46-9AF8-F53ABF0775F2}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_Aliases\" PackageName=\"Visual Studio Environment Package\"><Aliases Version=\"1.0\"><Alias Name=\"immed\" Definition=\"Tools.ImmediateMode\"/><Alias Name=\"props\" Definition=\"View.PropertiesWindow\"/><Alias Name=\"memory1\" Definition=\"debug.Memory1\"/><Alias Name=\"memory2\" Definition=\"debug.Memory2\"/><Alias Name=\"memory3\" Definition=\"debug.Memory3\"/><Alias Name=\"Lcase\" Definition=\"Edit.MakeLowercase\"/><Alias Name=\"memory4\" Definition=\"debug.Memory4\"/><Alias Name=\"locals\" Definition=\"debug.Locals\"/><Alias Name=\"kb\" Definition=\"debug.ListCallStack\"/><Alias Name=\"da\" Definition=\"debug.ListMemory /Ansi\"/><Alias Name=\"db\" Definition=\"debug.ListMemory /Format:OneByte\"/><Alias Name=\"DelEOL\" Definition=\"Edit.DeleteToEOL\"/><Alias Name=\"dc\" Definition=\"debug.ListMemory /Format:FourBytes /Ansi\"/><Alias Name=\"dd\" Definition=\"debug.ListMemory /Format:FourBytes\"/><Alias Name=\"df\" Definition=\"debug.ListMemory /Format:Float\"/><Alias Name=\"WordWrap\" Definition=\"Edit.ToggleWordWrap\"/><Alias Name=\"AddProj\" Definition=\"File.AddNewProject\"/><Alias Name=\"dq\" Definition=\"debug.ListMemory /Format:EightBytes\"/><Alias Name=\"du\" Definition=\"debug.ListMemory /Unicode\"/><Alias Name=\"designer\" Definition=\"View.ViewDesigner\"/><Alias Name=\"ListMembers\" Definition=\"Edit.ListMembers\"/><Alias Name=\"code\" Definition=\"View.ViewCode\"/><Alias Name=\"ParamInfo\" Definition=\"Edit.ParameterInfo\"/><Alias Name=\"DelHSp\" Definition=\"Edit.DeleteHorizontalWhiteSpace\"/><Alias Name=\"TileH\" Definition=\"Window.TileHorizontally\"/><Alias Name=\"save\" Definition=\"File.SaveSelectedItems\"/><Alias Name=\"?\" Definition=\"debug.Print\"/><Alias Name=\"callstack\" Definition=\"debug.CallStack\"/><Alias Name=\"open\" Definition=\"File.OpenFile\"/><Alias Name=\"TileV\" Definition=\"Window.TileVertically\"/><Alias Name=\"OutlineDefs\" Definition=\"Edit.CollapsetoDefinitions\"/><Alias Name=\"CloseAll\" Definition=\"Window.CloseAllDocuments\"/><Alias Name=\"ToggleBook\" Definition=\"Edit.ToggleBookmark\"/><Alias Name=\"InsertFile\" Definition=\"Edit.InsertFileAsText\"/><Alias Name=\"autos\" Definition=\"debug.Autos\"/><Alias Name=\"SaveAll\" Definition=\"File.SaveAll\"/><Alias Name=\"new\" Definition=\"File.NewFile\"/><Alias Name=\"d\" Definition=\"debug.ListMemory\"/><Alias Name=\"rtc\" Definition=\"debug.RunToCursor\"/><Alias Name=\"navigate\" Definition=\"View.WebBrowser\"/><Alias Name=\"g\" Definition=\"debug.Start\"/><Alias Name=\"k\" Definition=\"debug.ListCallStack\"/><Alias Name=\"format\" Definition=\"Edit.FormatSelection\"/><Alias Name=\"n\" Definition=\"debug.SetRadix\"/><Alias Name=\"p\" Definition=\"debug.StepOver\"/><Alias Name=\"q\" Definition=\"debug.StopDebugging\"/><Alias Name=\"r\" Definition=\"debug.ListRegisters\"/><Alias Name=\"t\" Definition=\"debug.StepInto\"/><Alias Name=\"~*kb\" Definition=\"debug.ListCallStack /AllThreads\"/><Alias Name=\"u\" Definition=\"debug.ListDisassembly\"/><Alias Name=\"nf\" Definition=\"File.NewFile\"/><Alias Name=\"registers\" Definition=\"debug.Registers\"/><Alias Name=\"LineCut\" Definition=\"Edit.LineCut\"/><Alias Name=\"memory\" Definition=\"memory1\"/><Alias Name=\"|\" Definition=\"debug.ListProcesses\"/><Alias Name=\"~\" Definition=\"debug.ListThreads\"/><Alias Name=\"np\" Definition=\"File.NewProject\"/><Alias Name=\"DelBOL\" Definition=\"Edit.DeleteToBOL\"/><Alias Name=\"~*k\" Definition=\"debug.ListCallStack /AllThreads\"/><Alias Name=\"NextBook\" Definition=\"Edit.NextBookmark\"/><Alias Name=\"TaskList\" Definition=\"View.TaskList\"/><Alias Name=\"Ucase\" Definition=\"Edit.MakeUppercase\"/><Alias Name=\"NewProj\" Definition=\"File.NewProject\"/><Alias Name=\"Untabify\" Definition=\"Edit.UntabifySelectedLines\"/><Alias Name=\"GotoBrace\" Definition=\"Edit.GotoBrace\"/><Alias Name=\"cls\" Definition=\"Edit.ClearAll\"/><Alias Name=\"shell\" Definition=\"Tools.Shell\"/><Alias Name=\"of\" Definition=\"File.OpenFile\"/><Alias Name=\"log\" Definition=\"Tools.LogCommandWindowOutput\"/><Alias Name=\"print\" Definition=\"File.Print\"/><Alias Name=\"op\" Definition=\"File.OpenProject\"/><Alias Name=\"eval\" Definition=\"debug.EvaluateStatement\"/><Alias Name=\"alias\" Definition=\"Tools.Alias\"/><Alias Name=\"watch\" Definition=\"debug.Watch\"/><Alias Name=\"cmd\" Definition=\"View.CommandWindow\"/><Alias Name=\"help\" Definition=\"Help.F1Help\"/><Alias Name=\"GotoLn\" Definition=\"Edit.GoTo\"/><Alias Name=\"disasm\" Definition=\"debug.Disassembly\"/><Alias Name=\"redo\" Definition=\"Edit.Redo\"/><Alias Name=\"undo\" Definition=\"Edit.Undo\"/><Alias Name=\"tabify\" Definition=\"Edit.TabifySelectedLines\"/><Alias Name=\"LineDel\" Definition=\"Edit.LineDelete\"/><Alias Name=\"StopOutlining\" Definition=\"Edit.CollapsetoDefinitions\"/><Alias Name=\"pr\" Definition=\"debug.StepOut\"/><Alias Name=\"SaveAs\" Definition=\"File.SaveSelectedItemsAs\"/><Alias Name=\"FullScreen\" Definition=\"View.FullScreen\"/><Alias Name=\"nav\" Definition=\"navigate\"/><Alias Name=\"SwapAnchor\" Definition=\"Edit.SwapAnchor\"/><Alias Name=\"exit\" Definition=\"File.Exit\"/><Alias Name=\"threads\" Definition=\"debug.Threads\"/><Alias Name=\"PrevBook\" Definition=\"Edit.PreviousBookmark\"/><Alias Name=\"close\" Definition=\"File.Close\"/><Alias Name=\"toolbox\" Definition=\"View.Toolbox\"/><Alias Name=\"bl\" Definition=\"debug.Breakpoints\"/><Alias Name=\"bp\" Definition=\"debug.ToggleBreakpoint\"/><Alias Name=\"??\" Definition=\"debug.QuickWatch\"/><Alias Name=\"StopFind\" Definition=\"Edit.FindInFiles /stop\"/><Alias Name=\"ClearBook\" Definition=\"Edit.ClearBookmarks\"/></Aliases></Category><Category name=\"Environment_CallBrowser\" Category=\"{50B05A5D-9174-48eb-851A-B1C616A0B43D}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_CallBrowser\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"CallBrowserSortAlphabetically\">true</PropertyValue><PropertyValue name=\"CallBrowserSortByAccess\">false</PropertyValue><PropertyValue name=\"CallBrowserShowFullyQualifiedNames\">true</PropertyValue></Category><Category name=\"Environment_ClassView\" Category=\"{40AF29AB-4C5B-412A-9CE8-465C4FCFE41D}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_ClassView\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"ClassViewShowProjectReferences\">true</PropertyValue><PropertyValue name=\"ClassViewShowHiddenTypesAndMembers\">true</PropertyValue><PropertyValue name=\"ClassViewTypesSortAlphabetically\">true</PropertyValue><PropertyValue name=\"ClassViewTypesSortByType\">false</PropertyValue><PropertyValue name=\"ClassViewTypesSortByAccess\">false</PropertyValue><PropertyValue name=\"ClassViewTypesGroupByType\">false</PropertyValue><PropertyValue name=\"ClassViewSearchResultsSortByBestMatch\">true</PropertyValue><PropertyValue name=\"ClassViewSearchResultsSortAlphabetically\">false</PropertyValue><PropertyValue name=\"ClassViewSearchResultsSortByType\">false</PropertyValue><PropertyValue name=\"ClassViewSearchResultsSortByAccess\">false</PropertyValue><PropertyValue name=\"ClassViewSearchResultsGroupByType\">false</PropertyValue><PropertyValue name=\"ClassViewMembersViewPublic\">true</PropertyValue><PropertyValue name=\"ClassViewMembersViewProtected\">true</PropertyValue><PropertyValue name=\"ClassViewMembersViewPrivate\">true</PropertyValue><PropertyValue name=\"ClassViewMembersViewOther\">true</PropertyValue><PropertyValue name=\"ClassViewMembersShowInherited\">false</PropertyValue><PropertyValue name=\"ClassViewShowExtensionMembers\">false</PropertyValue><PropertyValue name=\"ClassViewShowBaseTypes\">true</PropertyValue><PropertyValue name=\"ClassViewShowDerivedTypes\">true</PropertyValue><PropertyValue name=\"ClassViewMembersSortAlphabetically\">false</PropertyValue><PropertyValue name=\"ClassViewMembersSortByType\">true</PropertyValue><PropertyValue name=\"ClassViewMembersSortByAccess\">false</PropertyValue></Category><Category name=\"Environment_CommandBars\" Category=\"{B9D9C123-B500-4202-B887-57C829CBD08F}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_CommandBars\" PackageName=\"Visual Studio Environment Package\"><CommandBars Version=\"05072811\"><DefaultCustomizations><modify_toolbar Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,258,24\" DockRectangle=\"0,0,258,24\"/><modify_toolbar Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:0000000d\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,93,24\" DockRectangle=\"28,0,93,24\"/><modify_toolbar Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,213,24\" DockRectangle=\"21,0,213,24\"/><modify_toolbar Menu=\"{AA8EB8CD-7A51-11D0-92C3-00A0C9138C45}:00005dc0\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,305,24\" DockRectangle=\"33,0,305,24\"/><modify_toolbar Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,213,24\" DockRectangle=\"1,0,213,24\"/><modify_toolbar Menu=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000001\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,130,24\" DockRectangle=\"23,0,130,24\"/><modify_toolbar Menu=\"{CB26E292-901A-419C-B79D-49BD45C43929}:00002710\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,268,24\" DockRectangle=\"34,0,268,24\"/><modify_toolbar Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,162,24\" DockRectangle=\"27,0,162,24\"/><modify_toolbar Menu=\"{E148F049-C570-4F55-84A6-6DA870AF229E}:00001388\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,363,24\" DockRectangle=\"7,0,363,24\"/><modify_toolbar Menu=\"{E148F049-C570-4F55-84A6-6DA870AF229E}:000013d8\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,415,24\" DockRectangle=\"12,0,415,24\"/><modify_toolbar Menu=\"{E148F049-C570-4F55-84A6-6DA870AF229E}:00001838\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,190,24\" DockRectangle=\"17,0,190,24\"/><modify_toolbar Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,486,24\" DockRectangle=\"10,0,486,24\"/><modify_toolbar Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,234,24\" DockRectangle=\"11,0,234,24\"/><modify_toolbar Menu=\"{C7547851-4E3A-4E5B-9173-FA6E9C8BD82C}:0000271a\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,184,24\" DockRectangle=\"35,0,184,24\"/><modify_toolbar Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,290,24\" DockRectangle=\"16,0,290,24\"/><modify_toolbar Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000000b\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,203,24\" DockRectangle=\"22,0,203,24\"/><modify_toolbar Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,412,24\" DockRectangle=\"6,0,412,24\"/><modify_toolbar Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,137,24\" DockRectangle=\"20,0,137,24\"/><modify_toolbar Menu=\"{F65C9B12-9CC9-498A-AB19-6CBE3D2C6BC6}:00000002\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,86,24\" DockRectangle=\"30,0,86,24\"/><modify_toolbar Menu=\"{5D4C0442-C0A2-4BE8-9B4D-AB1C28450942}:00001000\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,133,24\" DockRectangle=\"8,0,133,24\"/><modify_toolbar Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,212,24\" DockRectangle=\"19,0,212,24\"/><modify_toolbar Menu=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005dc0\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,130,24\" DockRectangle=\"32,0,130,24\"/><modify_toolbar Menu=\"{2DC8D6BB-916C-4B80-9C52-FD8FC371ACC2}:00000100\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,188,24\" DockRectangle=\"26,0,188,24\"/><modify_toolbar Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,466,24\" DockRectangle=\"13,0,466,24\"/><modify_toolbar Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,276,24\" DockRectangle=\"25,0,276,24\"/><modify_toolbar Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,249,24\" DockRectangle=\"3,0,249,24\"/><modify_toolbar Menu=\"{D709F4D7-0165-472B-B966-105912D13DB8}:00000104\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,130,24\" DockRectangle=\"31,0,130,24\"/><modify_toolbar Menu=\"{5D8847D1-9A8A-431E-907A-62D9963F07FD}:00000104\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,133,24\" DockRectangle=\"37,0,133,24\"/><modify_toolbar Menu=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:0000050c\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,283,24\" DockRectangle=\"24,0,283,24\"/><modify_toolbar Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,458,24\" DockRectangle=\"2,0,458,24\"/><modify_toolbar Menu=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00003010\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,432,24\" DockRectangle=\"9,0,432,24\"/><modify_toolbar Menu=\"{C9522C54-50B0-48C9-A9B8-339140E0FA20}:00001801\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,268,24\" DockRectangle=\"14,0,268,24\"/><modify_toolbar Menu=\"{6E9EABA0-FDEE-4A1C-8758-E1DCA032A5D3}:00001802\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,297,24\" DockRectangle=\"15,0,297,24\"/><modify_toolbar Menu=\"{068E2583-0872-403B-AF4C-6C2A8F2D8C3E}:00001201\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,71,24\" DockRectangle=\"5,0,71,24\"/><modify_toolbar Menu=\"{068E2583-0872-403B-AF4C-6C2A8F2D8C3E}:00003203\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,203,24\" DockRectangle=\"18,0,203,24\"/><modify_toolbar Menu=\"{481A3758-A6D0-43E0-B1B8-4428BE962F8E}:00007202\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,431,24\" DockRectangle=\"4,0,431,24\"/><modify_toolbar Menu=\"{16BD08C3-403D-4B06-B57B-57B60E572241}:00001001\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,133,24\" DockRectangle=\"4,0,133,24\"/><modify_toolbar Menu=\"{9B3258D9-F00A-4519-9253-7909878E8882}:00001010\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,346,24\" DockRectangle=\"29,0,346,24\"/><add Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\"/><add Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000005\" CmdPri=\"08010001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\"/><add Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000014e\" CmdPri=\"00108001\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001403\" GroupPri=\"04000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\"/><add Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206d\" CmdPri=\"00080001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\"/><add Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206e\" CmdPri=\"000c0001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\"/><add Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206f\" CmdPri=\"000e0001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\"/><remove Cmd=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000500\" CmdPri=\"00210000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000171\" GroupPri=\"01000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000010\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000172\" GroupPri=\"03000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000000f\" CmdPri=\"02000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000172\" GroupPri=\"03000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000001a\" CmdPri=\"03000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000172\" GroupPri=\"03000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{6E87CFAD-6C05-4ADF-9CD7-3B7943875B7C}:00000010\" CmdPri=\"06500000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000115\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000177\" GroupPri=\"0b000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000151\" CmdPri=\"02000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000177\" GroupPri=\"0b000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000ea\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000eb\" CmdPri=\"02000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{23D49123-60AC-4D7E-939A-E01A4E176BEE}:00000001\" CmdPri=\"02000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000ee\" CmdPri=\"02500000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000002a\" CmdPri=\"03000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{E234E66E-BA64-4D71-B304-16F0A4C793F5}:00004010\" CmdPri=\"03000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:000008c5\" CmdPri=\"04000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5DD0BB59-7076-4C59-88D3-DE36931F63F0}:00000bb8\" CmdPri=\"50000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000502\" CmdPri=\"ff000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000178\" GroupPri=\"0d000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006c\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"01000000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000068\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"01000000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006b\" CmdPri=\"01300000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"01000000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000046\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000551\" GroupPri=\"01010000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000045\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000551\" GroupPri=\"01010000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000239\" CmdPri=\"01260000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000238\" CmdPri=\"012b0000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000098\" CmdPri=\"01300000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000097\" CmdPri=\"01400000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000127\" CmdPri=\"01000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:0000011b\" GroupPri=\"01000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"true\"/><remove Cmd=\"{D709F4D7-0165-472B-B966-105912D13DB8}:00002039\" CmdPri=\"00000000\" Group=\"{D709F4D7-0165-472B-B966-105912D13DB8}:00001009\" GroupPri=\"04000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5D8847D1-9A8A-431E-907A-62D9963F07FD}:00002039\" CmdPri=\"00000000\" Group=\"{5D8847D1-9A8A-431E-907A-62D9963F07FD}:00001009\" GroupPri=\"04000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000013a\" CmdPri=\"03000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000025\" GroupPri=\"05000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"true\"/><remove Cmd=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000409\" CmdPri=\"01000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:0000011a\" GroupPri=\"07000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"true\"/><remove Cmd=\"{E8B06F44-6D01-11D2-AA7D-00C04F990343}:000000ce\" CmdPri=\"00500000\" Group=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000064\" GroupPri=\"00100000\" Menu=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{4A79114A-19E4-11D3-B86B-00C04F79F802}:00000106\" CmdPri=\"00100000\" Group=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000068\" GroupPri=\"00400000\" Menu=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{4A79114A-19E4-11D3-B86B-00C04F79F802}:00000107\" CmdPri=\"00300000\" Group=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000068\" GroupPri=\"00400000\" Menu=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000300d\" CmdPri=\"01000000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002316\" GroupPri=\"01000000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000300f\" CmdPri=\"00f10000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000233b\" GroupPri=\"01020000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{F4394F71-4DFC-4268-84C3-7D9150C5C216}:00003007\" CmdPri=\"02000000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000233b\" GroupPri=\"01020000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003010\" CmdPri=\"00f00000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000233c\" GroupPri=\"01030000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{F4394F71-4DFC-4268-84C3-7D9150C5C216}:00003008\" CmdPri=\"02000000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:0000233c\" GroupPri=\"01030000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003000\" CmdPri=\"01000000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003002\" CmdPri=\"01010000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003001\" CmdPri=\"01020000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003100\" CmdPri=\"01030000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00003003\" CmdPri=\"01100000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{F4394F71-4DFC-4268-84C3-7D9150C5C216}:00003000\" CmdPri=\"02000000\" Group=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00002000\" GroupPri=\"01040000\" Menu=\"{B85579AA-8BE0-4C4F-A850-90902B317571}:00004000\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000012\" CmdPri=\"03000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001070\" GroupPri=\"01000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000011f\" CmdPri=\"0a000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001074\" GroupPri=\"05000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000120\" CmdPri=\"0b000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001074\" GroupPri=\"05000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000014d\" CmdPri=\"0e000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:0000107d\" GroupPri=\"07000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000046\" CmdPri=\"03000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001078\" GroupPri=\"01000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000045\" CmdPri=\"04000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001078\" GroupPri=\"01000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000014b\" CmdPri=\"0c000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:0000107c\" GroupPri=\"05000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000004\" CmdPri=\"02000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001011\" GroupPri=\"0b000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000004\" CmdPri=\"04000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001012\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000000b\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" CmdPri=\"05000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001106\" GroupPri=\"01000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000033\" CmdPri=\"03000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001107\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000ad\" CmdPri=\"09000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001118\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000006\" CmdPri=\"03000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001109\" GroupPri=\"07000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000008\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110d\" GroupPri=\"0b000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000009\" CmdPri=\"03000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110d\" GroupPri=\"0b000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000003\" CmdPri=\"03000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110e\" GroupPri=\"0d000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000065\" CmdPri=\"05000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"10000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000066\" CmdPri=\"06000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"10000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:000002a5\" CmdPri=\"07000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"10000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:0000006c\" CmdPri=\"08000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"10000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000ad\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001108\" GroupPri=\"01000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000065\" CmdPri=\"05000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:00000066\" CmdPri=\"06000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" IsPartialRemoval=\"true\"/><remove Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:000002a5\" CmdPri=\"07000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" IsPartialRemoval=\"true\"/><remove Cmd=\"{748813A7-657C-499D-81A0-2FFDF790711A}:0000006c\" CmdPri=\"08000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000014e\" CmdPri=\"00100000\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001402\" GroupPri=\"03000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:000053fc\" CmdPri=\"02000000\" Group=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00002f2f\" GroupPri=\"04000000\" Menu=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005dc0\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005404\" CmdPri=\"06000000\" Group=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00002f2f\" GroupPri=\"04000000\" Menu=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005dc0\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:0000540c\" CmdPri=\"07000000\" Group=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00002f2f\" GroupPri=\"04000000\" Menu=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005dc0\" IsPartialRemoval=\"true\"/><remove Cmd=\"{34E61A32-1C10-4052-ABE7-264C46302CE4}:00000002\" CmdPri=\"04000000\" Group=\"{4FE997E0-5BDC-41CD-8E02-A212DAD97CDA}:00000700\" GroupPri=\"01000000\" Menu=\"{2DC8D6BB-916C-4B80-9C52-FD8FC371ACC2}:00000100\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000017\" CmdPri=\"02000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055d\" GroupPri=\"06000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000016\" CmdPri=\"03000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055d\" GroupPri=\"06000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000015\" CmdPri=\"04000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055d\" GroupPri=\"06000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000030\" CmdPri=\"02000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000002f\" CmdPri=\"03000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000002e\" CmdPri=\"04000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000000c\" CmdPri=\"00000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055f\" GroupPri=\"08000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000000d\" CmdPri=\"00000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055f\" GroupPri=\"08000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{CB3675B8-701E-4F56-9167-2714E167FF3F}:00003008\" CmdPri=\"01000000\" Group=\"{CB3675B8-701E-4F56-9167-2714E167FF3F}:0000200b\" GroupPri=\"0f000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000029\" CmdPri=\"00000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:00000568\" GroupPri=\"10000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006c\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006c\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000068\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000068\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000074\" CmdPri=\"01200000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006b\" CmdPri=\"01300000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006b\" CmdPri=\"01300000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:000008ff\" CmdPri=\"01400000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB87333B-16C8-400E-BC8F-F6B890410582}:00000001\" CmdPri=\"00100000\" Group=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00001002\" GroupPri=\"00200000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000046\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000551\" GroupPri=\"00300000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000045\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000551\" GroupPri=\"00300000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000239\" CmdPri=\"01260000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"00500000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000238\" CmdPri=\"012b0000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"00500000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000098\" CmdPri=\"01300000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"00500000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000097\" CmdPri=\"01400000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"00500000\" Menu=\"{061317B2-F992-435E-A23D-9EAD4B972ED5}:00000102\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002907\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002908\" CmdPri=\"00200000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002909\" CmdPri=\"00300000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002071\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001088\" GroupPri=\"00200000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206d\" CmdPri=\"00200000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001089\" GroupPri=\"00600000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206e\" CmdPri=\"00300000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001089\" GroupPri=\"00600000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206f\" CmdPri=\"00400000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001089\" GroupPri=\"00600000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002120\" CmdPri=\"02000000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001077\" GroupPri=\"02000000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" IsPartialRemoval=\"true\"/><remove Cmd=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:00003113\" CmdPri=\"01000000\" Group=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:0000032a\" GroupPri=\"03000000\" Menu=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:0000050c\" IsPartialRemoval=\"true\"/><remove Cmd=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:00003114\" CmdPri=\"02000000\" Group=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:0000032a\" GroupPri=\"03000000\" Menu=\"{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}:0000050c\" IsPartialRemoval=\"true\"/><remove Cmd=\"{B11771F1-F6CB-467E-8B71-428B5CEDCA5F}:00000101\" CmdPri=\"01000000\" Group=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00020003\" GroupPri=\"01300000\" Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" IsPartialRemoval=\"true\"/><remove Cmd=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00002001\" CmdPri=\"01100000\" Group=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00020003\" GroupPri=\"01300000\" Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" IsPartialRemoval=\"true\"/><remove Cmd=\"{14BCA3AF-5E27-4307-8492-84AF8647EA7C}:00002001\" CmdPri=\"01100000\" Group=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00020003\" GroupPri=\"01300000\" Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" IsPartialRemoval=\"true\"/><remove Cmd=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00002001\" CmdPri=\"02030000\" Group=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:0000102b\" GroupPri=\"06000000\" Menu=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00003010\" IsPartialRemoval=\"true\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000002ac\" CmdPri=\"03000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" Width=\"65\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:000007c6\" CmdPri=\"04000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" Width=\"110\"/><modify Cmd=\"{E8B06F44-6D01-11D2-AA7D-00C04F990343}:000000c9\" CmdPri=\"00100000\" Group=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000064\" GroupPri=\"00100000\" Menu=\"{E8B06F42-6D01-11D2-AA7D-00C04F990343}:00000001\" Icon=\"{DDFE7DBB-66E4-4954-8A0F-36CFE15EB12E}:00000002\" Style=\"0\"/><modify Cmd=\"{CB26E292-901A-419C-B79D-49BD45C43929}:00000078\" CmdPri=\"01000000\" Group=\"{CB26E292-901A-419C-B79D-49BD45C43929}:00002711\" GroupPri=\"00000000\" Menu=\"{CB26E292-901A-419C-B79D-49BD45C43929}:00002710\" Width=\"115\"/><modify Cmd=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001132\" CmdPri=\"00000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001068\" GroupPri=\"00000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Width=\"105\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:000000f5\" CmdPri=\"02000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001070\" GroupPri=\"01000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" BeginGroup=\"false\" Width=\"70\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000034\" CmdPri=\"00000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001071\" GroupPri=\"02000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:0000000a\" BeginGroup=\"false\"/><modify Cmd=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001085\" CmdPri=\"00000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001073\" GroupPri=\"04000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000118\" CmdPri=\"09000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001076\" GroupPri=\"06000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Icon=\"{8328592C-227C-11D3-B870-00C04F79F802}:00000017\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000118\" CmdPri=\"09000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001076\" GroupPri=\"06000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Icon=\"{8328592C-227C-11D3-B870-00C04F79F802}:00000017\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000118\" CmdPri=\"09000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001076\" GroupPri=\"06000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00000010\" Icon=\"{8328592C-227C-11D3-B870-00C04F79F802}:00000017\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000088\" CmdPri=\"05000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001079\" GroupPri=\"02000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000056\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000088\" CmdPri=\"05000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001079\" GroupPri=\"02000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000056\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000088\" CmdPri=\"05000000\" Group=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001079\" GroupPri=\"02000000\" Menu=\"{D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}:00001077\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000056\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000c9\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000100e\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" Icon=\"{634F8946-FFF0-491F-AF41-B599FC20D561}:00000009\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000079\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001010\" GroupPri=\"09000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" Icon=\"{634F8946-FFF0-491F-AF41-B599FC20D561}:0000000e\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000027\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001011\" GroupPri=\"0b000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000001\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:0000000e\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000079\" CmdPri=\"02000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001012\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000000b\" Icon=\"{634F8946-FFF0-491F-AF41-B599FC20D561}:0000000e\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:0000000c\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001107\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" Icon=\"{EB28B762-7E54-492B-9336-4853994FE349}:0000000b\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:0000000c\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001107\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" Icon=\"{EB28B762-7E54-492B-9336-4853994FE349}:0000000b\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000012\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110b\" GroupPri=\"09000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000012\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110b\" GroupPri=\"09000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000007\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110c\" GroupPri=\"0b000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:0000000a\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000032\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110f\" GroupPri=\"0f000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" BeginGroup=\"false\" Width=\"55\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:000002a4\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"10000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000101\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000011\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000006d\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110a\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000003\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000006d\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:0000110a\" GroupPri=\"03000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000003\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:000002a4\" CmdPri=\"01000000\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000011\" BeginGroup=\"false\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000004\" CmdPri=\"08008001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{732ABE75-CD80-11D0-A2DB-00AA00A3EFFF}:00000005\" CmdPri=\"08010001\" Group=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00001110\" GroupPri=\"05000000\" Menu=\"{732ABE74-CD80-11D0-A2DB-00AA00A3EFFF}:00000107\" Icon=\"{2B671D3D-AB51-434A-8D38-CBF1728530BB}:00000009\" BeginGroup=\"false\"/><modify Cmd=\"{A764E897-518D-11D2-9A89-00C04F79EFC3}:00002101\" CmdPri=\"00200000\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001401\" GroupPri=\"01000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" Icon=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000005\" Style=\"0\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00002c13\" CmdPri=\"00100000\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001403\" GroupPri=\"04000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" BeginGroup=\"false\"/><modify Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00002c13\" CmdPri=\"00100000\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001403\" GroupPri=\"04000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000014e\" CmdPri=\"00108001\" Group=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00001403\" GroupPri=\"04000000\" Menu=\"{A764E896-518D-11D2-9A89-00C04F79EFC3}:00000103\" Icon=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000003\" BeginGroup=\"false\"/><modify Cmd=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005212\" CmdPri=\"01000000\" Group=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00002f2f\" GroupPri=\"04000000\" Menu=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:00005dc0\" Icon=\"{5BF14E63-E267-4787-B20B-B814FD043B38}:0000000d\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000006\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055a\" GroupPri=\"04000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000008\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000031\" CmdPri=\"01000000\" Group=\"{234A7FC1-CFE9-4335-9E82-061F86E402C1}:0000055e\" GroupPri=\"07000000\" Menu=\"{74D21312-2AEE-11D1-8BFB-00A0C90F26F7}:00000601\" Icon=\"{9CD93C42-CEEF-45AB-B1B5-6040880C9543}:00000083\" BeginGroup=\"false\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206d\" CmdPri=\"00080001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000005\" BeginGroup=\"false\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206e\" CmdPri=\"000c0001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000006\" BeginGroup=\"false\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:0000206f\" CmdPri=\"000e0001\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001094\" GroupPri=\"00100000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" BeginGroup=\"false\" Width=\"55\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002889\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:0000107a\" GroupPri=\"00200000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002889\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:0000107a\" GroupPri=\"00200000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002889\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:0000107a\" GroupPri=\"00200000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002889\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:0000107a\" GroupPri=\"00200000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000008\" BeginGroup=\"true\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002932\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001073\" GroupPri=\"00300000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000013\" BeginGroup=\"true\"/><modify Cmd=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00002932\" CmdPri=\"00100000\" Group=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00001073\" GroupPri=\"00300000\" Menu=\"{F5EAC013-F209-453B-A2F3-CDF43821C24E}:00000302\" Icon=\"{FB41A027-57C5-4F83-9508-C326DCE6D943}:00000013\" BeginGroup=\"true\"/><modify Cmd=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00000012\" CmdPri=\"00400000\" Group=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00020001\" GroupPri=\"01000000\" Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" Width=\"55\"/><modify Cmd=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00000014\" CmdPri=\"00300000\" Group=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00020002\" GroupPri=\"01100000\" Menu=\"{4BCF92C9-7FEA-4913-AF26-F93582BA9C7A}:00010002\" Icon=\"{E5FD8797-928D-4720-99C6-D9241615013A}:00000001\" BeginGroup=\"false\"/><modify Cmd=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:0000200d\" CmdPri=\"01000000\" Group=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00001002\" GroupPri=\"00500000\" Menu=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00003010\" Width=\"55\"/><modify Cmd=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00002300\" CmdPri=\"01000000\" Group=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00001009\" GroupPri=\"00900000\" Menu=\"{ADC1BC7B-958B-4548-9F9F-10FC49099825}:00003010\" Icon=\"{AD7818C6-4420-4479-B0CB-CE68EFF9C2C8}:00000006\" BeginGroup=\"false\"/></DefaultCustomizations><UserCustomizations><modify_toolbar Menu=\"{D2DAE92B-0569-4B2D-958E-0D8212558BA2}:00000100\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,93,26\" DockRectangle=\"7,0,93,26\"/><modify_toolbar Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,141,26\" DockRectangle=\"14,0,141,26\"/><modify_toolbar Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000420\" Visibility=\"hide\" FullScreen=\"hide\" Dock=\"top\" Row=\"2\" FloatRectangle=\"0,0,1036,26\" DockRectangle=\"0,0,1036,26\"/><modify_toolbar Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" Visibility=\"auto\" FullScreen=\"hide\" Dock=\"top\" Row=\"1\" FloatRectangle=\"0,0,107,26\" DockRectangle=\"6,0,107,26\"/><add Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000006c\" CmdPri=\"00480001\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"01000000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\"/><add Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000068\" CmdPri=\"00900001\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000550\" GroupPri=\"01000000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\"/><add Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000013a\" CmdPri=\"01880001\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000025\" GroupPri=\"05000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\"/><remove Cmd=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000501\" CmdPri=\"00200000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000171\" GroupPri=\"01000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000de\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000171\" GroupPri=\"01000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000014b\" CmdPri=\"03000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000171\" GroupPri=\"01000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000e0\" CmdPri=\"05000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000171\" GroupPri=\"01000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000002c\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000173\" GroupPri=\"05000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:0000001e\" CmdPri=\"02000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000173\" GroupPri=\"05000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{FF3BEE8B-9F4C-4DB1-ACC7-CC853DF3D673}:00000020\" CmdPri=\"00500000\" Group=\"{1CDC6AC9-86A4-4B40-9BD4-8D67A0EBFA99}:00001020\" GroupPri=\"07000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{F9FF219D-9853-4AE2-A85A-1303E6F783E9}:00002002\" CmdPri=\"00100000\" Group=\"{F9FF219D-9853-4AE2-A85A-1303E6F783E9}:00001020\" GroupPri=\"08000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{C9DD4A59-47FB-11D2-83E7-00C04F9902C1}:0000f014\" CmdPri=\"05ff0000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{6E87CFAD-6C05-4ADF-9CD7-3B7943875B7C}:00000100\" CmdPri=\"06000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000170\" CmdPri=\"06010000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{C9DD4A59-47FB-11D2-83E7-00C04F9902C1}:0000f023\" CmdPri=\"06100000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{30947EBE-9147-45F9-96CF-401BFC671A82}:00002000\" CmdPri=\"07000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000174\" GroupPri=\"09000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{ACD78B4D-A02F-47B2-B34A-120A7B1F0F70}:00001300\" CmdPri=\"01000000\" Group=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:0000017a\" GroupPri=\"0f000000\" Menu=\"{D309F791-903F-11D0-9EFC-00A0C911004F}:00000001\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000088\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000552\" GroupPri=\"01020000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:00000089\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000552\" GroupPri=\"01020000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000004c\" CmdPri=\"01000000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000004e\" CmdPri=\"01100000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000004d\" CmdPri=\"01200000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}:0000004b\" CmdPri=\"01500000\" Group=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:00000553\" GroupPri=\"01030000\" Menu=\"{9ADF33D0-8AAD-11D0-B606-00A0C922E851}:0000000e\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000b3\" CmdPri=\"07000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:0000011b\" GroupPri=\"01000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000128\" CmdPri=\"09000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:0000011b\" GroupPri=\"01000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{C9DD4A59-47FB-11D2-83E7-00C04F9902C1}:0000f011\" CmdPri=\"03000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000403\" GroupPri=\"02000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{C9DD4A59-47FB-11D2-83E7-00C04F9902C1}:0000f012\" CmdPri=\"04000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000403\" GroupPri=\"02000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{C9DD4A59-47FB-11D2-83E7-00C04F9902C1}:0000f013\" CmdPri=\"05000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000403\" GroupPri=\"02000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:00000103\" CmdPri=\"01000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000011\" GroupPri=\"03000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000f8\" CmdPri=\"06000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000011\" GroupPri=\"03000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000f9\" CmdPri=\"07000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000011\" GroupPri=\"03000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}:000000fa\" CmdPri=\"08000000\" Group=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000011\" GroupPri=\"03000000\" Menu=\"{C9DD4A58-47FB-11D2-83E7-00C04F9902C1}:00000421\" IsPartialRemoval=\"false\"/><remove Cmd=\"{1CAA6E8A-B001-4EAC-AA08-08928D950E30}:00000103\" CmdPri=\"00100000\" Group=\"{1CAA6E8A-B001-4EAC-AA08-08928D950E30}:00001400\" GroupPri=\"00000000\" Menu=\"{1CAA6E8A-B001-4EAC-AA08-08928D950E30}:00001300\" IsPartialRemoval=\"false\"/></UserCustomizations></CommandBars><PropertyValue name=\"ShowLargeButtons\">false</PropertyValue><PropertyValue name=\"ShowScreenTips\">true</PropertyValue><PropertyValue name=\"ShowScreenTipShortcutKeys\">false</PropertyValue></Category><Category name=\"Environment_DefaultEditors\" Category=\"{68161AEE-BCA8-4214-B2E4-7875263E49C5}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_DefaultEditors\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"NumberOfMappings\">0</PropertyValue><PropertyValue name=\"NumberOfDefaultEditors\">0</PropertyValue></Category><Category name=\"Environment_Expansions\" Category=\"{0B9A10B2-C0D0-4a6a-8353-115E9CACF34A}\" Package=\"{F5E7E720-1401-11d1-883B-0000F87579D2}\" RegisteredName=\"Environment_Expansions\" PackageName=\"Text Management Package\"><Expansions Version=\"1.0\"><Language Guid=\"{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}\"><Path><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\application\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\common code patterns\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\data\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\fundamentals\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\os\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\other\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\windowsforms\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual Basic\\My Code Snippets\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\WPF\\\"/><Folder Name=\"%InstallRoot%\\VB\\Snippets\\%LCID%\\Test\\\"/></Path></Language><Language Guid=\"{B2F072B0-ABC1-11D0-9D62-00C04FD9DFD9}\"><Path><Folder Name=\"%vsspv_vs_install_directory%\\Common7\\IDE\\VC\\Snippets\\%LCID%\\Visual C++\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual C++\\My Code Snippets\\\"/></Path></Language><Language Guid=\"{694DD9B6-B865-4C5B-AD85-86356E9C88DC}\"><Path><Folder Name=\"%InstallRoot%\\VC#\\Snippets\\%LCID%\\Visual C#\\\"/><Folder Name=\"%InstallRoot%\\VC#\\Snippets\\%LCID%\\Refactoring\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual C#\\My Code Snippets\\\"/><Folder Name=\"%InstallRoot%\\VC#\\Snippets\\%LCID%\\NetFX30\\\"/><Folder Name=\"%InstallRoot%\\VC#\\Snippets\\%LCID%\\Test\\\"/></Path></Language><Language Guid=\"{a764e898-518d-11d2-9a89-00c04f79efc3}\"><Path><Folder Name=\"%InstallRoot%\\Web\\Snippets\\CSS\\%LCID%\\CSS\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual Web Developer\\My CSS Snippets\\\"/></Path></Language><Language Guid=\"{9BBFD173-9770-47DC-B191-651B7FF493CD}\"><Path><Folder Name=\"%InstallRoot%\\Web\\Snippets\\Html\\%LCID%\\Html\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual Web Developer\\My HTML Snippets\\\"/></Path></Language><Language Guid=\"{58E975A0-F8FE-11D2-A6AE-00104BCC7269}\"><Path><Folder Name=\"%InstallRoot%\\Web\\Snippets\\HTML\\%LCID%\\HTML\\\"/><Folder Name=\"%InstallRoot%\\Web\\Snippets\\HTML\\%LCID%\\ASP.NET\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\Visual Web Developer\\My HTML Snippets\\\"/></Path></Language><Language Guid=\"{f6819a78-a205-47b5-be1c-675b3c7f0b8e}\"><Path><Folder Name=\"%InstallRoot%\\xml\\%LCID%\\Snippets\\\"/><Folder Name=\"%MyDocs%\\Code Snippets\\XML\\My Xml Snippets\\\"/></Path></Language></Expansions></Category><Category name=\"Environment_ExtensionManager\" Category=\"{539391d7-9414-4715-b033-8669db0abee1}\" Package=\"{e7576c05-1874-450c-9e98-cf3a0897a069}\" RegisteredName=\"Environment_ExtensionManager\" PackageName=\"ExtensionManagerPackage\"><PropertyValue name=\"AutomaticallyCheckForUpdates2\">True</PropertyValue><PropertyValue name=\"CustomRepositories\">&lt;ExtensionRepositoryConfigList Capacity=\"0\" xmlns=\"clr-namespace:Microsoft.VisualStudio.ExtensionManager;assembly=Microsoft.VisualStudio.ExtensionEngineContract\" /&gt;</PropertyValue><PropertyValue name=\"EnableAdminExtensions\">True</PropertyValue></Category><Category name=\"Environment_ExternalTools\" Category=\"{E8FAE9E8-FBA2-4474-B134-AB0FFCFB291D}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_ExternalTools\" PackageName=\"Visual Studio Environment Package\"><ExternalTools/></Category><Category name=\"Environment_FindSymbol\" Category=\"{C93260BC-0C07-484a-8188-6F4763BD7FD4}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_FindSymbol\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"FindOptionsIsExpanded\">false</PropertyValue><PropertyValue name=\"LookinReferencesIsChecked\">true</PropertyValue><PropertyValue name=\"MatchCaseIsChecked\">false</PropertyValue><PropertyValue name=\"Type\">2</PropertyValue><PropertyValue name=\"SelectedScope\">{A5A527EA-CF0A-4ABF-B501-EAFE6B3BA5C6}</PropertyValue><PropertyValue name=\"SelectedScopeSubScope\">0</PropertyValue></Category><Category name=\"Environment_FontsAndColors\" Category=\"{1EDA5DD4-927A-43a7-810E-7FD247D0DA1D}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_FontsAndColors\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"Version\">2</PropertyValue><FontsAndColors Version=\"2.0\"><Theme Id=\"{1DED0138-47CE-435E-84EF-9EC1F439B749}\"/><Categories/></FontsAndColors></Category><Category name=\"Environment_General\" Category=\"{435668FE-00FF-429B-A970-87426874A21B}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_General\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"WindowListItems\">10</PropertyValue><PropertyValue name=\"MRUListItems\">10</PropertyValue><PropertyValue name=\"ShowStatusBar\">true</PropertyValue><PropertyValue name=\"DockedWinClose\">true</PropertyValue><PropertyValue name=\"DockedWinAuto\">false</PropertyValue><PropertyValue name=\"AutoAdjustExperience\">true</PropertyValue><PropertyValue name=\"VisualEffectsAllowed\">131071</PropertyValue><PropertyValue name=\"UseTitleCaseOnMenu\">true</PropertyValue><PropertyValue name=\"DisableTouch\">true</PropertyValue><PropertyValue name=\"EnablePerMonitorAwareness\">true</PropertyValue><PropertyValue name=\"ForceWindowsTheme\">true</PropertyValue><PropertyValue name=\"UseMinimalMode\">true</PropertyValue></Category><Category name=\"Environment_KeyBindings\" Category=\"{F09035F1-80D2-4312-8EC4-4D354A4BCB4C}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_KeyBindings\" PackageName=\"Visual Studio Environment Package\"><Version>17.0.0.0</Version><KeyboardShortcuts><ScopeDefinitions><Scope Name=\"Team Explorer\" ID=\"{7AA20502-9463-47B7-BF43-341BAF51157C}\"/><Scope Name=\"VC Dialog Editor\" ID=\"{543E0C02-8C85-4E43-933A-5EF320E3431F}\"/><Scope Name=\"Rest Api Editor\" ID=\"{5703B403-55E7-4C63-8C88-A8F52C7A45C5}\"/><Scope Name=\"Find All References Tool Window\" ID=\"{1FA1FD06-3592-4D1D-AC75-0B953320140C}\"/><Scope Name=\"Test Explorer Playlist Tab\" ID=\"{D2F6510A-150A-4F3F-9D7C-8E38622D26F7}\"/><Scope Name=\"XML (Text) Editor\" ID=\"{FA3CD31E-987B-443A-9B81-186104E8DAC1}\"/><Scope Name=\"Text Editor\" ID=\"{8B382828-6202-11D1-8870-0000F87579D2}\"/><Scope Name=\"Work Item Results View\" ID=\"{7026002D-01F6-44E7-95CF-A896C00DA3F8}\"/><Scope Name=\"Solution Explorer\" ID=\"{3AE79031-E1BC-11D0-8F78-00A0C9110057}\"/><Scope Name=\"Query Designer\" ID=\"{B2C40B32-3A37-4CA9-97B9-FA44248B69FF}\"/><Scope Name=\"C# Editor with Encoding\" ID=\"{08467B34-B90F-4D91-BDCA-EB8C8CF3033A}\"/><Scope Name=\"Watch Windows\" ID=\"{90243340-BD7A-11D0-93EF-00A0C90F2734}\"/><Scope Name=\"WebBrowser\" ID=\"{E8B06F41-6D01-11D2-AA7D-00C04F990343}\"/><Scope Name=\"CSS Editor\" ID=\"{A5401142-F49D-43DB-90B1-F57BA349E55C}\"/><Scope Name=\"Breakpoints Window\" ID=\"{BE4D7042-BA3F-11D2-840E-00C04F9902C1}\"/><Scope Name=\"Graphics Designers\" ID=\"{58961B49-13E0-48C0-9258-13CBC4D40279}\"/><Scope Name=\"DataSet Editor\" ID=\"{B334A759-F450-40A5-BE2A-65937BCD5415}\"/><Scope Name=\"View Designer\" ID=\"{B968E165-98E0-41F0-8FBE-A8ED1D246A90}\"/><Scope Name=\"Terminal\" ID=\"{D212F56B-C48A-434C-A121-1C5D80B59B9F}\"/><Scope Name=\"Visual Basic Editor\" ID=\"{2C015C70-C72C-11D0-88C3-00A0C9110049}\"/><Scope Name=\"Global\" ID=\"{5EFC7975-14BC-11CF-9B2B-00AA00573819}\"/><Scope Name=\"HTML Editor\" ID=\"{40D31677-CBC0-4297-A9EF-89D907823A98}\"/><Scope Name=\"Autos Window\" ID=\"{F2E84780-2AF1-11D1-A7FA-00A0C9110051}\"/><Scope Name=\"Work Item Query View\" ID=\"{B6303490-B828-410C-9216-AE727D0E282D}\"/><Scope Name=\"Call Stack Window\" ID=\"{0504FF91-9D61-11D0-A794-00A0C9110051}\"/><Scope Name=\"Team Foundation Build Detail Editor\" ID=\"{86306A97-84F2-4F5A-889B-1318501AEB5F}\"/><Scope Name=\"Database Designer\" ID=\"{CFF78A9B-78A3-45A3-9142-0267AFC261FA}\"/><Scope Name=\"Work Item Editor\" ID=\"{40A91D9D-8076-4D28-87C5-5AF9F0ACFE0F}\"/><Scope Name=\"Live Unit Testing Tab\" ID=\"{AA9A73A5-B2BF-400B-B57C-58F166FF1C56}\"/><Scope Name=\"JSON Editor\" ID=\"{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}\"/><Scope Name=\"C# Editor\" ID=\"{A6C744A8-0E4A-4FC6-886A-064283054674}\"/><Scope Name=\"VC String Editor\" ID=\"{58442DA9-10DA-4AA9-A2AF-96E4D481379B}\"/><Scope Name=\"Merge Editor Window\" ID=\"{9A9A8AAA-ACD2-4DB6-BD81-8D64176C52B6}\"/><Scope Name=\"Visual Basic Editor with Encoding\" ID=\"{6C33E1AA-1401-4536-AB67-0E21E6E569DA}\"/><Scope Name=\"Settings Designer\" ID=\"{515231AD-C9DC-4AA3-808F-E1B65E72081C}\"/><Scope Name=\"Windows Forms Designer\" ID=\"{BA09E2AF-9DF2-4068-B2F0-4C7E5CC19E2F}\"/><Scope Name=\"Git\" ID=\"{7605A6B0-118F-45BF-95CC-7DB110BF44FD}\"/><Scope Name=\"VC Accelerator Editor\" ID=\"{EB56D0B5-BEE7-4D0C-8BE6-88A8ED256695}\"/><Scope Name=\"Modules Window\" ID=\"{37ABA9BE-445A-11D3-9949-00C04F68FD0A}\"/><Scope Name=\"Managed Resources Editor\" ID=\"{FEA4DCC9-3645-44CD-92E7-84B55A16465C}\"/><Scope Name=\"Parallel Stacks Window\" ID=\"{B9A151CE-EF7C-4FE1-A6AA-4777E6E518F3}\"/><Scope Name=\"Locals Window\" ID=\"{4A18F9D0-B838-11D0-93EB-00A0C90F2734}\"/><Scope Name=\"Difference Viewer\" ID=\"{79D52DDF-52BC-43F1-9663-B3E85CDCA912}\"/><Scope Name=\"Table Designer\" ID=\"{4194FEE5-6777-419F-A5FC-47A536DF1BDB}\"/><Scope Name=\"HTML Editor Design View\" ID=\"{CB3FCFEA-03DF-11D1-81D2-00A0C91BBEE3}\"/><Scope Name=\"{244DF7EA-D757-4B52-B92C-EFD2D3402C91}\" ID=\"{244DF7EA-D757-4B52-B92C-EFD2D3402C91}\"/><Scope Name=\"HTML Editor Source View\" ID=\"{CB3FCFEB-03DF-11D1-81D2-00A0C91BBEE3}\"/><Scope Name=\"VC Image Editor\" ID=\"{C0BA70ED-069E-412B-9C06-7442E28A11B9}\"/><Scope Name=\"Test Explorer\" ID=\"{E1B7D1F8-9B3C-49B1-8F4F-BFC63A88835D}\"/><Scope Name=\"XML Schema Designer\" ID=\"{DEE6CEF9-3BCA-449A-82A6-FC757D6956FB}\"/><Scope Name=\"{9E0531FA-FD8C-4007-BD8D-DBB03EBDB4B3}\" ID=\"{9E0531FA-FD8C-4007-BD8D-DBB03EBDB4B3}\"/></ScopeDefinitions><DefaultShortcuts><Shortcut Command=\"View.ViewCode\" Scope=\"Global\">F7</Shortcut><Shortcut Command=\"View.ViewDesigner\" Scope=\"Global\">Shift+F7</Shortcut></DefaultShortcuts><ShortcutsScheme>Visual C++ 6</ShortcutsScheme><UserShortcuts><RemoveShortcut Command=\"debug.Graphics.StartGraphicsDebugging\" Scope=\"Global\">Alt+F5</RemoveShortcut><RemoveShortcut Command=\"Edit.FormatSelection\" Scope=\"Global\">Alt+F8</RemoveShortcut><RemoveShortcut Command=\"View.NavigateBackward\" Scope=\"Global\">Ctrl+-</RemoveShortcut><RemoveShortcut Command=\"Edit.GoToFile\" Scope=\"Global\">Ctrl+1, Ctrl+F</RemoveShortcut><RemoveShortcut Command=\"Edit.GoToFile\" Scope=\"Global\">Ctrl+1, F</RemoveShortcut><RemoveShortcut Command=\"Refactor.Rename\" Scope=\"Global\">Ctrl+R, Ctrl+R</RemoveShortcut><RemoveShortcut Command=\"View.NavigateForward\" Scope=\"Global\">Ctrl+Shift+-</RemoveShortcut><RemoveShortcut Command=\"Edit.SizeControlLeft\" Scope=\"Global\">Ctrl+Shift+Left Arrow</RemoveShortcut><RemoveShortcut Command=\"Edit.SizeControlRight\" Scope=\"Global\">Ctrl+Shift+Right Arrow</RemoveShortcut><RemoveShortcut Command=\"Edit.GoToFile\" Scope=\"Global\">Ctrl+Shift+T</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.TeamExplorerNavigateBackward\" Scope=\"Team Explorer\">Alt+Left Arrow</RemoveShortcut><RemoveShortcut Command=\"Edit.FormatSelection\" Scope=\"Text Editor\">Ctrl+K, Ctrl+F</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToMyWork\" Scope=\"Global\">Ctrl+0, M</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToMyWork\" Scope=\"Global\">Ctrl+0, Ctrl+M</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToPendingChanges\" Scope=\"Global\">Ctrl+0, P</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToPendingChanges\" Scope=\"Global\">Ctrl+0, Ctrl+P</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToDocuments\" Scope=\"Global\">Ctrl+0, D</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToDocuments\" Scope=\"Global\">Ctrl+0, Ctrl+D</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToBuilds\" Scope=\"Global\">Ctrl+0, B</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToBuilds\" Scope=\"Global\">Ctrl+0, Ctrl+B</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToConnect\" Scope=\"Global\">Ctrl+0, C</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToConnect\" Scope=\"Global\">Ctrl+0, Ctrl+C</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToWebAccess\" Scope=\"Global\">Ctrl+0, A</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToWebAccess\" Scope=\"Global\">Ctrl+0, Ctrl+A</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToSettings\" Scope=\"Global\">Ctrl+0, S</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToSettings\" Scope=\"Global\">Ctrl+0, Ctrl+S</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToHome\" Scope=\"Global\">Ctrl+0, H</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToHome\" Scope=\"Global\">Ctrl+0, Ctrl+H</RemoveShortcut><RemoveShortcut Command=\"View.GitRepositoryWindow\" Scope=\"Global\">Ctrl+0, R</RemoveShortcut><RemoveShortcut Command=\"View.GitRepositoryWindow\" Scope=\"Global\">Ctrl+0, Ctrl+R</RemoveShortcut><RemoveShortcut Command=\"View.GitWindow\" Scope=\"Global\">Ctrl+0, G</RemoveShortcut><RemoveShortcut Command=\"View.GitWindow\" Scope=\"Global\">Ctrl+0, Ctrl+G</RemoveShortcut><RemoveShortcut Command=\"Team.Git.GoToGitSynchronization\" Scope=\"Global\">Ctrl+0, Y</RemoveShortcut><RemoveShortcut Command=\"Team.Git.GoToGitSynchronization\" Scope=\"Global\">Ctrl+0, Ctrl+Y</RemoveShortcut><RemoveShortcut Command=\"Team.Git.GoToGitActiveRepositories\" Scope=\"Global\">Ctrl+0, Ctrl+E</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToWorkItems\" Scope=\"Global\">Ctrl+0, W</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.GoToWorkItems\" Scope=\"Global\">Ctrl+0, Ctrl+W</RemoveShortcut><RemoveShortcut Command=\"Team.Git.GoToGitActiveRepositories\" Scope=\"Global\">Ctrl+0, E</RemoveShortcut><RemoveShortcut Command=\"TeamFoundationContextMenus.Commands.TeamExplorerNavigateForward\" Scope=\"Team Explorer\">Alt+Right Arrow</RemoveShortcut><RemoveShortcut Command=\"View.NavigateForward\" Scope=\"Global\">Ctrl+Alt+Right Arrow</RemoveShortcut><Shortcut Command=\"View.NavigateBackward\" Scope=\"Global\">Ctrl+Alt+Left Arrow</Shortcut><Shortcut Command=\"Edit.FormatSelection\" Scope=\"Global\">Ctrl+Shift+F7</Shortcut><Shortcut Command=\"Project.SetasStartupProject\" Scope=\"Global\">F6</Shortcut><Shortcut Command=\"Refactor.Rename\" Scope=\"Global\">Shift+F6</Shortcut><Shortcut Command=\"View.NavigateForward\" Scope=\"Global\">Ctrl+]</Shortcut><Shortcut Command=\"View.NavigateBackward\" Scope=\"Global\">Ctrl+[</Shortcut><Shortcut Command=\"debug.StartupProjectProperties\" Scope=\"Global\">Ctrl+Shift+F5</Shortcut><Shortcut Command=\"Edit.FormatSelection\" Scope=\"Global\">Alt+F7</Shortcut><Shortcut Command=\"Edit.GoToFile\" Scope=\"Global\">Ctrl+Shift+G</Shortcut></UserShortcuts></KeyboardShortcuts></Category><Category name=\"Environment_NewProjectDialogPreferredLanguage\" Category=\"{77E22C0C-9F4A-4bfb-AD3A-3F3F4711E9ED}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_NewProjectDialogPreferredLanguage\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"HasNewProjectDialogPreferredLanguage\">false</PropertyValue><PropertyValue name=\"DefaultToLargeIconView\">false</PropertyValue></Category><Category name=\"Environment_ObjectBrowser\" Category=\"{D98D784D-88BF-4EBF-B039-C146EC14F472}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_ObjectBrowser\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"ObjectBrowserViewNamespaces\">false</PropertyValue><PropertyValue name=\"ObjectBrowserShowHiddenTypesAndMembers\">false</PropertyValue><PropertyValue name=\"ObjectBrowserTypesSortAlphabetically\">true</PropertyValue><PropertyValue name=\"ObjectBrowserTypesSortByType\">false</PropertyValue><PropertyValue name=\"ObjectBrowserTypesSortByAccess\">false</PropertyValue><PropertyValue name=\"ObjectBrowserTypesGroupByType\">false</PropertyValue><PropertyValue name=\"ObjectBrowserSearchResultsSortByBestMatch\">true</PropertyValue><PropertyValue name=\"ObjectBrowserSearchResultsSortAlphabetically\">false</PropertyValue><PropertyValue name=\"ObjectBrowserSearchResultsSortByType\">false</PropertyValue><PropertyValue name=\"ObjectBrowserSearchResultsSortByAccess\">false</PropertyValue><PropertyValue name=\"ObjectBrowserSearchResultsGroupByType\">false</PropertyValue><PropertyValue name=\"ObjectBrowserMembersViewPublic\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersViewProtected\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersViewPrivate\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersViewOther\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersShowInherited\">false</PropertyValue><PropertyValue name=\"ObjectBrowserShowExtensionMembers\">false</PropertyValue><PropertyValue name=\"ObjectBrowserShowBaseTypes\">true</PropertyValue><PropertyValue name=\"ObjectBrowserShowDerivedTypes\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersSortAlphabetically\">false</PropertyValue><PropertyValue name=\"ObjectBrowserMembersSortByType\">true</PropertyValue><PropertyValue name=\"ObjectBrowserMembersSortByAccess\">false</PropertyValue><PropertyValue name=\"ObjectBrowserScope\">{B1BA9461-FC54-45B3-A484-CB6DD0B95C94}</PropertyValue><PropertyValue name=\"ObjectBrowserScopeSubScope\">0</PropertyValue></Category><Category name=\"Environment_OutputWindow\" Category=\"{07C6CD04-100B-4f3e-BA93-DD1363E8D8E3}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_OutputWindow\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"OutputWindowWordWrap\">false</PropertyValue></Category><Category name=\"Environment_PreviewFeatures\" Category=\"{fca50351-5e03-4e31-9cc0-ab59a9c6b829}\" Package=\"{7ac58323-1325-44db-a4d5-2823a1426a13}\" RegisteredName=\"Environment_PreviewFeatures\" PackageName=\"FeatureFlagsPackage\"><PropertyValue name=\"HasFeatureFlags\">True</PropertyValue></Category><Category name=\"Environment_PropertiesWindow\" Category=\"{731a3cc7-de5e-49ca-9115-9a03e46624b0}\" Package=\"{7494682b-37a0-11d2-a273-00c04f8ef4ff}\" RegisteredName=\"Environment_PropertiesWindow\" PackageName=\"Windows Forms Designer Package\"><PropertyValue name=\"PbrsAlpha\">0</PropertyValue><PropertyValue name=\"PbrsShowDesc\">1</PropertyValue></Category><Category name=\"Environment_SimplifiedToolsOptions\" Category=\"{13BEDEE6-9051-4809-B9E3-2EEC749EB15A}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_SimplifiedToolsOptions\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"SupportsSimpleToolsOptions\">false</PropertyValue><PropertyValue name=\"ShowingAllPages\">true</PropertyValue><PropertyValue name=\"ShowFlatList\">false</PropertyValue></Category><Category name=\"Environment_SolutionExplorer\" Category=\"{6fa9062f-82b2-4725-ba6d-58cf7cba7dea}\" Package=\"{cf6a5c16-83b0-4d04-b702-195c35c6e887}\" RegisteredName=\"Environment_SolutionExplorer\" PackageName=\"SolutionNavigatorPackage\"><PropertyValue name=\"SearchFileContents\">false</PropertyValue><PropertyValue name=\"SearchExternalItems\">false</PropertyValue></Category><Category name=\"Environment_TabsAndWindows\" Category=\"{CCF27946-63D9-4806-91BA-EEBF5F3DFA4A}\" Package=\"{5E56B3DB-7964-4588-8D49-D3523AB7BDB9}\" RegisteredName=\"Environment_TabsAndWindows\" PackageName=\"Environment Package Window Management\"><PropertyValue name=\"BoldSelectedTabs\">True</PropertyValue><PropertyValue name=\"ColorizeDocumentTabs\">False</PropertyValue><PropertyValue name=\"CurrentFileGroupProvider\">15146f07-d8dd-40a1-8b10-dd4ba62587cb</PropertyValue><PropertyValue name=\"DirtyIndicatorPreference\">Default</PropertyValue><PropertyValue name=\"DocumentTabPanelDockLocation\">Top</PropertyValue><PropertyValue name=\"EnableIndependentTabWell\">True</PropertyValue><PropertyValue name=\"EnableIndependentToolWindows\">False</PropertyValue><PropertyValue name=\"EnablePreviewTab\">True</PropertyValue><PropertyValue name=\"EnableSingleClickPreviewInFindResults\">True</PropertyValue><PropertyValue name=\"EnableSingleClickPreviewInNavigateTo\">True</PropertyValue><PropertyValue name=\"EnableSingleClickPreviewInSolutionExplorer\">False</PropertyValue><PropertyValue name=\"GroupTabsByProject\">True</PropertyValue><PropertyValue name=\"IsMultiRowTabsEnabled\">False</PropertyValue><PropertyValue name=\"MaintainPinStatus\">False</PropertyValue><PropertyValue name=\"MaximumTabWidth\">215</PropertyValue><PropertyValue name=\"MinimumTabWidth\">60</PropertyValue><PropertyValue name=\"ShowAutoHiddenWindowsOnHover\">False</PropertyValue><PropertyValue name=\"ShowAutoHideChannelsOnChanges\">True</PropertyValue><PropertyValue name=\"ShowHideButtonInTabs\">True</PropertyValue><PropertyValue name=\"ShowHideButtonInWell\">False</PropertyValue><PropertyValue name=\"ShowInvisibleTabsInItalic\">False</PropertyValue><PropertyValue name=\"ShowPinButtonInUnpinnedTabs\">True</PropertyValue><PropertyValue name=\"ShowPinnedTabsInSeparateRow\">False</PropertyValue><PropertyValue name=\"TabSortOrder\">Alphabetical</PropertyValue></Category><Category name=\"Environment_Toolbox\" Category=\"{481999F2-7479-4e03-83D5-BE808BA142DF}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_Toolbox\" PackageName=\"Visual Studio Environment Package\">\n\t\t\t<PropertyValue name=\"DeletedItemCount\">0</PropertyValue>\n\t\t\t<PropertyValue name=\"DeletedTabCount\">0</PropertyValue>\n\t\t\t<PropertyValue name=\"ItemCount\">0</PropertyValue>\n\t\t\t<PropertyValue name=\"ShowAllTabs\">false</PropertyValue>\n\t\t\t<PropertyValue name=\"TabCount\">0</PropertyValue>\n\t\t\t<PropertyValue name=\"Version\">1</PropertyValue>\n\t\t</Category><Category name=\"Environment_UnifiedFind\" Category=\"{DF00ADDF-C14C-4ffd-9325-634FD605850B}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_UnifiedFind\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"Options\">MatchCase=0 WholeWord=0 Hidden=0 Up=0 Selection=0 Block=0 KeepCase=0 SubFolders=1 KeepOpen=0 NameOnly=0 Append=0 Plain Files Find</PropertyValue><PropertyValue name=\"DialogOptions\">MatchCase=0 WholeWord=0 Hidden=0 Up=0 Selection=0 Block=0 KeepCase=0 SubFolders=1 KeepOpen=0 NameOnly=0 Append=0 Plain Files Find</PropertyValue><PropertyValue name=\"AdornmentOptions\">MatchCase=0 WholeWord=0 Hidden=1 Up=0 Selection=0 Block=0 KeepCase=0 SubFolders=0 KeepOpen=0 NameOnly=0 Append=0 Plain Document Find</PropertyValue><PropertyValue name=\"Result list\">1</PropertyValue><PropertyValue name=\"Result list justB\">1</PropertyValue><PropertyValue name=\"ShowEndOfSearch\">0</PropertyValue><PropertyValue name=\"ShowAllReplacementsNotAllowed\">1</PropertyValue><PropertyValue name=\"ShowSomeReplacementsNotAllowed\">1</PropertyValue><PropertyValue name=\"ShowFindStart\">0</PropertyValue><PropertyValue name=\"ShowCancelBeforeReplacementsMade\">0</PropertyValue><PropertyValue name=\"ShowReplaceInFilesWarning\">1</PropertyValue><PropertyValue name=\"ShowCancelEntireReplaceAfterCancellingSCC\">1</PropertyValue><PropertyValue name=\"AutomaticallyLimitSearchToSelection\">1</PropertyValue><PropertyValue name=\"FindTextFromEditor\">1</PropertyValue><PropertyValue name=\"HitReplaced\">0</PropertyValue><PropertyValue name=\"EditableFindInFilesResults\">1</PropertyValue><PropertyValue name=\"Syntax\">Regex</PropertyValue><PropertyValue name=\"NumberOfScopes\">0</PropertyValue></Category><Category name=\"Environment_WindowLayout\" Category=\"{eb4ba109-a9db-4445-bd09-e7604bcdce84}\" Package=\"{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}\" RegisteredName=\"Environment_WindowLayout\" PackageName=\"Visual Studio Environment Package\"><PropertyValue name=\"Version\">7</PropertyValue><PropertyValue name=\"WindowProfileNames\">Design|debug|NoToolWin</PropertyValue><Design><WindowProfile xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:wm=\"clr-namespace:Microsoft.VisualStudio.Platform.WindowManagement;assembly=Microsoft.VisualStudio.Platform.WindowManagement\" Name=\"Design\" xmlns=\"clr-namespace:Microsoft.VisualStudio.PlatformUI.Shell;assembly=Microsoft.VisualStudio.Shell.ViewManager\"><MainSite FloatingTop=\"43\" FloatingLeft=\"72\" FloatingHeight=\"774\" FloatingWidth=\"1296\"><AutoHideRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\" DockRestriction=\"AlwaysFloating\"><AutoHideChannel Orientation=\"Vertical\"><AutoHideGroup><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/></AutoHideGroup><AutoHideGroup><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{25f7e850-ffa1-11d0-b63f-00a0c922e851}\" DockedHeight=\"768\" DockedWidth=\"204.8\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{873151d0-cf2e-48cc-b4bf-ad0394f6a3c3}\" DockedHeight=\"768\" DockedWidth=\"204.8\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Right\" Orientation=\"Vertical\" IsVisible=\"True\"><AutoHideGroup IsVisible=\"True\"><wm:ToolWindowView OnTopWhenLastInContext=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{6d4078d1-5951-4ed1-ac0e-0a8099c1cce5}\" IsVisible=\"True\" DockedHeight=\"537.6\" DockedWidth=\"307.2\" AutoHideWidth=\"307.2\" AutoHideHeight=\"537.6\" FloatingHeight=\"537.6\" FloatingWidth=\"307.2\"/></AutoHideGroup><AutoHideGroup IsVisible=\"True\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{6324226f-61b6-4f28-92ee-18d4b5fe1e48}\" IsVisible=\"True\" DockedHeight=\"473\" DockedWidth=\"450\" FloatingTop=\"344\" FloatingLeft=\"451\" FloatingHeight=\"480\" FloatingWidth=\"462\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Top\"/><AutoHideChannel Dock=\"Bottom\"/><DockRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup Orientation=\"Vertical\" DockedHeight=\"768\" DockedWidth=\"348.16\"><TabGroup DockedHeight=\"768\" DockedWidth=\"348.16\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{74946827-37a0-11d2-a273-00c04f8ef4ff}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView Name=\"ST:0:0:{099ca9ea-0ae4-4e31-a7e4-fe09bd1715cc}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><ViewBookmark Name=\"ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}\" DockedHeight=\"768\" DockedWidth=\"348.16\"/><wm:ToolWindowView Name=\"ST:0:0:{9f3ec988-1174-4746-a66a-3969715d1fc7}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5b6781c0-e99d-11d0-9954-00a0c91bc8e5}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1cba9826-3184-4799-a184-784e41b56398}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{38ed9834-0c97-445b-bd1d-f78f3e08afac}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{402dc223-d700-4029-866f-acee803f3f0c}\" DockedHeight=\"768\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/></TabGroup><TabGroup DockedHeight=\"768\" DockedWidth=\"348.16\"><ViewBookmark Name=\"ST:0:0:{25f7e850-ffa1-11d0-b63f-00a0c922e851}\" DockedHeight=\"768\" DockedWidth=\"348.16\"/><ViewBookmark Name=\"ST:0:0:{873151d0-cf2e-48cc-b4bf-ad0394f6a3c3}\" DockedHeight=\"768\" DockedWidth=\"348.16\"/></TabGroup></DockGroup><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><wm:WMDocumentGroupContainer IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><wm:WMDocumentGroup IsVisible=\"True\"><wm:ToolWindowView Name=\"ST:0:0:{387cb18d-6153-4156-9257-9ac3f9207bbe}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c79b74ff-f1d7-4c94-aefa-4d22bfe1b1f9}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{46c87f81-5a06-43a8-9e25-85d33bac49f8}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}\" FloatingHeight=\"384\" FloatingWidth=\"512\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{99b8fa2f-ab90-4f57-9c32-949f146f1914}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1820bae5-c385-4492-9de5-e35c9cf17b18}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMDocumentGroup><wm:WMDocumentGroup DockedHeight=\"589\"><ViewBookmark Name=\"ST:0:0:{6324226f-61b6-4f28-92ee-18d4b5fe1e48}\" AccessOrder=\"1\" ViewBookmarkType=\"DocumentWell\" DockedHeight=\"589\"/></wm:WMDocumentGroup></wm:WMDocumentGroupContainer><ViewBookmark Name=\"ST:0:0:{6324226f-61b6-4f28-92ee-18d4b5fe1e48}\"/><TabGroup IsVisible=\"True\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingTop=\"339\" FloatingLeft=\"658\" FloatingHeight=\"475\" FloatingWidth=\"348\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingTop=\"339\" FloatingLeft=\"658\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}\" IsVisible=\"True\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingTop=\"339\" FloatingLeft=\"658\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{131369f2-062d-44a2-8671-91ff31efb4f4}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c9c0ae26-aa77-11d2-b3f0-0000f87570ee}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{07cd18b4-3ba1-11d2-890a-0060083196c6}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{2d7728c2-de0a-45b5-99aa-89b609dfde73}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView Name=\"ST:0:0:{3addf8e2-81cc-41a0-9785-dbd2d86064bd}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{0db31cc8-2322-4f59-a610-1fdc8423df77}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{4a791147-19e4-11d3-b86b-00c04f79f802}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd58-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{57dc5d59-11c2-4955-a7b4-d7699d677e93}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{dd1ddd20-d59b-11da-a94d-0800200c9a66}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{a34b1c5d-6d37-4a0c-a8b0-99f8e8158b48}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingTop=\"76.8\" FloatingLeft=\"819.2\" FloatingHeight=\"475\" FloatingWidth=\"348\"/></TabGroup><ViewBookmark Name=\"ST:0:0:{6d4078d1-5951-4ed1-ac0e-0a8099c1cce5}\" DockedHeight=\"537.6\" DockedWidth=\"307.2\"/></DockGroup><TabGroup DockedHeight=\"192\" DockedWidth=\"100\"><ViewBookmark Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"192\" DockedWidth=\"100\"/></TabGroup></DockGroup><TabGroup DockedHeight=\"384\" DockedWidth=\"348.16\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{eefa5220-e298-11d0-8f78-00a0c9110057}\" DockedHeight=\"384\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{66dba47c-61df-11d2-aa79-00c04f990343}\" DockedHeight=\"384\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/></TabGroup></DockGroup><TabGroup IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"755\" FloatingLeft=\"97\" FloatingHeight=\"192\" FloatingWidth=\"1247\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{be4d7042-ba3f-11d2-840e-00c04f9902c1}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{28836128-fc2c-11d2-a433-00c04f72d18a}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{588470cc-84f8-4a57-9ac4-86bca0625ff4}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5415ea3a-d813-4948-b51e-562082ce0887}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{d212f56b-c48a-434c-a121-1c5d80b59b9f}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"755\" FloatingLeft=\"97\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView OnTopWhenLastInContext=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{271d5c74-3ac9-4328-910a-713e65f6ec25}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"755\" FloatingLeft=\"97\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView OnTopWhenLastInContext=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e9f0ab1f-5649-4620-a7d9-ee96788df550}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"755\" FloatingLeft=\"97\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f564}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f565}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{905da7d1-18fd-4a46-8d0f-a5ff58ada9de}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{519e8a32-1c95-4a42-956f-2cee2f28eb0f}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{92547016-2bd0-4dfe-bd4f-5b52bdce0037}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView Name=\"ST:0:0:{b869198c-f673-46d2-83ae-64f515277716}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView Name=\"ST:0:0:{b8399b49-7330-487b-9235-7d2e969d0a79}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView Name=\"ST:0:0:{778b5376-ad77-4751-acdc-f3d18343f8dd}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887920-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887921-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{68487888-204a-11d3-87eb-00c04f7971a5}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{fdffccf2-5f63-404f-86ad-33693f544948}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd5b-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{cdbdee54-b399-484b-b763-db2c3393d646}\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/></TabGroup></DockGroup></DockRoot></AutoHideRoot></MainSite><wm:WMFloatSite Id=\"6e4472e6-cace-48f9-b614-3be7d9a324bf\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"ae0bc153-6cd1-4268-9be4-676e55ffee9a\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"2cb3caac-fce8-4f0b-8a14-4f28f81d3bab\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"f0fe3778-5c46-4f10-af3c-e512376ce867\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"15a960f5-842c-47f7-b7c2-6ba410faacdb\"><wm:ToolWindowView Name=\"ST:0:0:{a2eaf38f-a0ad-4503-91f8-5f004a69a040}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"bf065142-1fa8-4ce2-af07-02a0ffc49fa1\"><wm:ToolWindowView Name=\"ST:0:0:{31fc2115-5126-4a87-b2f7-77eaab65048b}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"85f85686-f210-455c-bd5f-c780957f0c1c\"><wm:ToolWindowView Name=\"ST:0:0:{a0ca7712-7bb0-4324-9af0-70e70d5ecc2d}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"6cf41953-c805-42bd-b9be-fe8390c9410b\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><TabGroup FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"3b84fbf5-d23b-41cb-bdbf-bf49b233d1b8\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ca4b8ff5-bfc7-11d2-9929-00c04f68fdaf}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"66cfadb9-ff43-4c0d-83d2-ed87d1f9ebf7\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{51c76317-9037-4cf2-a20a-6206fd30b4a1}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"5e6e16f2-aea1-4997-8701-5fcfe36d833a\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><TabGroup FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e62ce6a0-b439-11d0-a79d-00a0c9110051}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{8d263989-ff4b-4a78-90c8-b2ba3fa69311}\" FloatingTop=\"400\" FloatingLeft=\"400\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"f62387d4-6176-4ba2-9398-e82a652d0141\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{37aba9be-445a-11d3-9949-00c04f68fd0a}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"ff630f17-9703-43f9-b36e-dc0f5fd36a48\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf577b8c-4134-11d2-83e5-00c04f9902c1}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"9211e869-445f-4a6d-892c-c4bb6f19bacc\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{f2e84780-2af1-11d1-a7fa-00a0c9110051}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"156dca93-143a-4abf-b90b-d40e27a6f0fd\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{4a18f9d0-b838-11d0-93eb-00a0c90f2734}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"76808826-05d0-4157-8422-c9176c47e21c\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><TabGroup FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"435def6e-b5e3-4860-813a-68a7acd5559f\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"ec592922-5364-4465-83e5-ff64d68679ad\"><wm:ToolWindowView Name=\"ST:0:0:{637792aa-f332-4bb5-be6c-066b0e88eced}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"d75ca342-4f73-42c0-b320-6a33c3aed6ca\"><wm:ToolWindowView Name=\"ST:0:0:{ed485b08-5acf-4ce9-8e13-699174ea0201}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"880bb59e-1405-4b2b-9b6c-38c31c2ca448\"><wm:ToolWindowView Name=\"ST:0:0:{ecdd9ee0-ac6b-11d0-89f9-00a0c9110055}\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"37732ef8-f8ce-457f-9b2a-62c7abc4e2df\"><TabGroup><wm:ToolWindowView Name=\"ST:1:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/><wm:ToolWindowView Name=\"ST:2:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/><wm:ToolWindowView Name=\"ST:0:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"9038eb44-8f73-44ea-868a-cf060a5fde10\" FloatingTop=\"755\" FloatingLeft=\"97\" FloatingHeight=\"192\" FloatingWidth=\"1247\"><TabGroup DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"><ViewBookmark Name=\"ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{be4d7042-ba3f-11d2-840e-00c04f9902c1}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{28836128-fc2c-11d2-a433-00c04f72d18a}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{588470cc-84f8-4a57-9ac4-86bca0625ff4}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{5415ea3a-d813-4948-b51e-562082ce0887}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:1:0:{d212f56b-c48a-434c-a121-1c5d80b59b9f}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{271d5c74-3ac9-4328-910a-713e65f6ec25}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{e9f0ab1f-5649-4620-a7d9-ee96788df550}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f564}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f565}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{905da7d1-18fd-4a46-8d0f-a5ff58ada9de}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{519e8a32-1c95-4a42-956f-2cee2f28eb0f}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{92547016-2bd0-4dfe-bd4f-5b52bdce0037}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{b869198c-f673-46d2-83ae-64f515277716}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{b8399b49-7330-487b-9235-7d2e969d0a79}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{778b5376-ad77-4751-acdc-f3d18343f8dd}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{0f887920-c2b6-11d2-9375-0080c747d9a0}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{0f887921-c2b6-11d2-9375-0080c747d9a0}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{68487888-204a-11d3-87eb-00c04f7971a5}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{fdffccf2-5f63-404f-86ad-33693f544948}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{73f6dd5b-437e-11d3-b88e-00c04f79f802}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/><ViewBookmark Name=\"ST:0:0:{cdbdee54-b399-484b-b763-db2c3393d646}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"1235\" FloatingHeight=\"192\" FloatingWidth=\"1247\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"1840260f-c5b0-4929-bf14-0e8adc785918\" FloatingTop=\"339\" FloatingLeft=\"658\" FloatingHeight=\"475\" FloatingWidth=\"348\"><TabGroup DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"><ViewBookmark Name=\"ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{131369f2-062d-44a2-8671-91ff31efb4f4}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{c9c0ae26-aa77-11d2-b3f0-0000f87570ee}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{07cd18b4-3ba1-11d2-890a-0060083196c6}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{2d7728c2-de0a-45b5-99aa-89b609dfde73}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{3addf8e2-81cc-41a0-9785-dbd2d86064bd}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{0db31cc8-2322-4f59-a610-1fdc8423df77}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{4a791147-19e4-11d3-b86b-00c04f79f802}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{73f6dd58-437e-11d3-b88e-00c04f79f802}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{57dc5d59-11c2-4955-a7b4-d7699d677e93}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{dd1ddd20-d59b-11da-a94d-0800200c9a66}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{a34b1c5d-6d37-4a0c-a8b0-99f8e8158b48}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/><ViewBookmark Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" ViewBookmarkType=\"Raft\" DockedHeight=\"468\" DockedWidth=\"336\" FloatingHeight=\"475\" FloatingWidth=\"348\"/></TabGroup></wm:WMFloatSite></WindowProfile></Design><debug><WindowProfile xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:wm=\"clr-namespace:Microsoft.VisualStudio.Platform.WindowManagement;assembly=Microsoft.VisualStudio.Platform.WindowManagement\" Name=\"debug\" xmlns=\"clr-namespace:Microsoft.VisualStudio.PlatformUI.Shell;assembly=Microsoft.VisualStudio.Shell.ViewManager\"><MainSite FloatingTop=\"43\" FloatingLeft=\"72\" FloatingHeight=\"774\" FloatingWidth=\"1296\"><AutoHideRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\" DockRestriction=\"AlwaysFloating\"><AutoHideChannel Orientation=\"Vertical\"><AutoHideGroup><wm:ToolWindowView Name=\"ST:0:0:{a2eaf38f-a0ad-4503-91f8-5f004a69a040}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"614.4\" FloatingWidth=\"256\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Right\" Orientation=\"Vertical\" IsVisible=\"True\"><AutoHideGroup IsVisible=\"True\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}\" IsVisible=\"True\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}\" IsVisible=\"True\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{131369f2-062d-44a2-8671-91ff31efb4f4}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingTop=\"76.8\" FloatingLeft=\"819.2\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView Name=\"ST:0:0:{31fc2115-5126-4a87-b2f7-77eaab65048b}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"614.4\" FloatingWidth=\"348.16\"/><wm:ToolWindowView Name=\"ST:0:0:{a0ca7712-7bb0-4324-9af0-70e70d5ecc2d}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"614.4\" FloatingWidth=\"512\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Top\"/><AutoHideChannel Dock=\"Bottom\"/><DockRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup Orientation=\"Vertical\" DockedHeight=\"768\" DockedWidth=\"358.4\"><TabGroup DockedHeight=\"652.8\" DockedWidth=\"358.4\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{74946827-37a0-11d2-a273-00c04f8ef4ff}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{099ca9ea-0ae4-4e31-a7e4-fe09bd1715cc}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView Name=\"ST:0:0:{9f3ec988-1174-4746-a66a-3969715d1fc7}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5b6781c0-e99d-11d0-9954-00a0c91bc8e5}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><ViewBookmark Name=\"ST:0:0:{a2eaf38f-a0ad-4503-91f8-5f004a69a040}\" DockedHeight=\"652.8\" DockedWidth=\"358.4\"/></TabGroup><TabGroup DockedHeight=\"768\" DockedWidth=\"358.4\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{25f7e850-ffa1-11d0-b63f-00a0c922e851}\" DockedHeight=\"768\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{873151d0-cf2e-48cc-b4bf-ad0394f6a3c3}\" DockedHeight=\"768\" DockedWidth=\"358.4\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/></TabGroup></DockGroup><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><TabGroup DockedHeight=\"153.6\" DockedWidth=\"819.2\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ca4b8ff5-bfc7-11d2-9929-00c04f68fdaf}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{51c76317-9037-4cf2-a20a-6206fd30b4a1}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{8d263989-ff4b-4a78-90c8-b2ba3fa69311}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"199.68\" FloatingWidth=\"512\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{37aba9be-445a-11d3-9949-00c04f68fd0a}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/></TabGroup><DockGroup IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><wm:WMDocumentGroupContainer IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><wm:WMDocumentGroup><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{387cb18d-6153-4156-9257-9ac3f9207bbe}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c79b74ff-f1d7-4c94-aefa-4d22bfe1b1f9}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{46c87f81-5a06-43a8-9e25-85d33bac49f8}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}\" FloatingHeight=\"384\" FloatingWidth=\"512\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{99b8fa2f-ab90-4f57-9c32-949f146f1914}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf577b8c-4134-11d2-83e5-00c04f9902c1}\" FloatingHeight=\"384\" FloatingWidth=\"358.4\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1820bae5-c385-4492-9de5-e35c9cf17b18}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></wm:WMDocumentGroup></wm:WMDocumentGroupContainer><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\"><TabGroup IsVisible=\"True\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingTop=\"466\" FloatingLeft=\"978\" FloatingHeight=\"192\" FloatingWidth=\"1450\"><wm:ToolWindowView ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" IconIndex=\"3\" IconResourceID=\"5123\" IconResourcePackage=\"c9dd4a57-47fb-11d2-83e7-00c04f9902c1\" Name=\"ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingTop=\"466\" FloatingLeft=\"978\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView Name=\"ST:0:0:{637792aa-f332-4bb5-be6c-066b0e88eced}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" IconIndex=\"2\" IconResourceID=\"5123\" IconResourcePackage=\"c9dd4a57-47fb-11d2-83e7-00c04f9902c1\" Name=\"ST:0:0:{be4d7042-ba3f-11d2-840e-00c04f9902c1}\" IsVisible=\"True\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingTop=\"466\" FloatingLeft=\"978\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}\" IsVisible=\"True\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingTop=\"466\" FloatingLeft=\"978\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{588470cc-84f8-4a57-9ac4-86bca0625ff4}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5415ea3a-d813-4948-b51e-562082ce0887}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f564}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f565}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{905da7d1-18fd-4a46-8d0f-a5ff58ada9de}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{519e8a32-1c95-4a42-956f-2cee2f28eb0f}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{92547016-2bd0-4dfe-bd4f-5b52bdce0037}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView Name=\"ST:0:0:{b869198c-f673-46d2-83ae-64f515277716}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView Name=\"ST:0:0:{b8399b49-7330-487b-9235-7d2e969d0a79}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><wm:ToolWindowView Name=\"ST:0:0:{778b5376-ad77-4751-acdc-f3d18343f8dd}\" DockedHeight=\"178.255208333333\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/></TabGroup><TabGroup IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingTop=\"411\" FloatingLeft=\"809\" FloatingHeight=\"192\" FloatingWidth=\"719\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" IconIndex=\"5\" IconResourceID=\"5123\" IconResourcePackage=\"c9dd4a57-47fb-11d2-83e7-00c04f9902c1\" Name=\"ST:0:0:{f2e84780-2af1-11d1-a7fa-00a0c9110051}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingTop=\"411\" FloatingLeft=\"809\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" IconIndex=\"4\" IconResourceID=\"5123\" IconResourcePackage=\"c9dd4a57-47fb-11d2-83e7-00c04f9902c1\" Name=\"ST:0:0:{4a18f9d0-b838-11d0-93eb-00a0c90f2734}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingTop=\"411\" FloatingLeft=\"809\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" IconIndex=\"9\" IconResourceID=\"5123\" IconResourcePackage=\"c9dd4a57-47fb-11d2-83e7-00c04f9902c1\" Name=\"ST:1:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingTop=\"411\" FloatingLeft=\"809\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887920-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887921-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{fdffccf2-5f63-404f-86ad-33693f544948}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd5b-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{cdbdee54-b399-484b-b763-db2c3393d646}\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}\" IsVisible=\"True\" DockedHeight=\"185\" DockedWidth=\"463\" FloatingTop=\"529\" FloatingLeft=\"796\" FloatingHeight=\"341\" FloatingWidth=\"721\"/></TabGroup></DockGroup><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{6d4078d1-5951-4ed1-ac0e-0a8099c1cce5}\" IsSelected=\"True\" DockedHeight=\"537.6\" DockedWidth=\"307.2\" AutoHideWidth=\"307.2\" AutoHideHeight=\"537.6\" FloatingHeight=\"537.6\" FloatingWidth=\"307.2\"/></DockGroup><TabGroup DockedHeight=\"192\" DockedWidth=\"100\"><ViewBookmark Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"192\" DockedWidth=\"100\"/></TabGroup></DockGroup><TabGroup DockedHeight=\"652.8\" DockedWidth=\"348.16\"><wm:ToolWindowView Name=\"ST:0:0:{ed485b08-5acf-4ce9-8e13-699174ea0201}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><ViewBookmark Name=\"ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/><ViewBookmark Name=\"ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/><ViewBookmark Name=\"ST:0:0:{131369f2-062d-44a2-8671-91ff31efb4f4}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c9c0ae26-aa77-11d2-b3f0-0000f87570ee}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{eefa5220-e298-11d0-8f78-00a0c9110057}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{ecdd9ee0-ac6b-11d0-89f9-00a0c9110055}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{2d7728c2-de0a-45b5-99aa-89b609dfde73}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView Name=\"ST:0:0:{3addf8e2-81cc-41a0-9785-dbd2d86064bd}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{0db31cc8-2322-4f59-a610-1fdc8423df77}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{66dba47c-61df-11d2-aa79-00c04f990343}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{4a791147-19e4-11d3-b86b-00c04f79f802}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd58-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{57dc5d59-11c2-4955-a7b4-d7699d677e93}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{07cd18b4-3ba1-11d2-890a-0060083196c6}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1cba9826-3184-4799-a184-784e41b56398}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{38ed9834-0c97-445b-bd1d-f78f3e08afac}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{402dc223-d700-4029-866f-acee803f3f0c}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{dd1ddd20-d59b-11da-a94d-0800200c9a66}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{a34b1c5d-6d37-4a0c-a8b0-99f8e8158b48}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><ViewBookmark Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/><ViewBookmark Name=\"ST:0:0:{31fc2115-5126-4a87-b2f7-77eaab65048b}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/><ViewBookmark Name=\"ST:0:0:{a0ca7712-7bb0-4324-9af0-70e70d5ecc2d}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/></TabGroup></DockGroup><TabGroup IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{d212f56b-c48a-434c-a121-1c5d80b59b9f}\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"864\" FloatingLeft=\"841\" FloatingHeight=\"198\" FloatingWidth=\"739\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{271d5c74-3ac9-4328-910a-713e65f6ec25}\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e9f0ab1f-5649-4620-a7d9-ee96788df550}\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{28836128-fc2c-11d2-a433-00c04f72d18a}\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"635\" FloatingLeft=\"562\" FloatingHeight=\"341\" FloatingWidth=\"721\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e62ce6a0-b439-11d0-a79d-00a0c9110051}\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"953\" FloatingLeft=\"193\" FloatingHeight=\"148\" FloatingWidth=\"1450\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{68487888-204a-11d3-87eb-00c04f7971a5}\" IsVisible=\"True\" DockedHeight=\"273\" DockedWidth=\"727\" FloatingTop=\"990\" FloatingLeft=\"572\" FloatingHeight=\"197\" FloatingWidth=\"1686\"/></TabGroup></DockGroup></DockRoot></AutoHideRoot></MainSite><wm:WMFloatSite Id=\"4973bbe5-8391-426b-aa45-2cf6c652f5a9\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"d04be2aa-968e-4fc3-b166-d5b96ca86a8b\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"f60517ea-af19-46ba-bba9-098d6bce53c5\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"56e5a3dd-4300-419f-948a-a72e0decb9de\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"f6b3744d-0a35-4475-a213-d6b116656a98\" FloatingTop=\"864\" FloatingLeft=\"841\" FloatingHeight=\"198\" FloatingWidth=\"739\"><ViewBookmark Name=\"ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}\" ViewBookmarkType=\"Raft\" DockedHeight=\"191\" DockedWidth=\"727\" FloatingHeight=\"198\" FloatingWidth=\"739\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"6d11c49a-8f64-44af-86fa-5b5162e4d01b\" FloatingTop=\"411\" FloatingLeft=\"809\" FloatingHeight=\"192\" FloatingWidth=\"719\"><TabGroup DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"><ViewBookmark Name=\"ST:0:0:{f2e84780-2af1-11d1-a7fa-00a0c9110051}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{4a18f9d0-b838-11d0-93eb-00a0c90f2734}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:1:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:2:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:3:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:4:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{0f887920-c2b6-11d2-9375-0080c747d9a0}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{0f887921-c2b6-11d2-9375-0080c747d9a0}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{fdffccf2-5f63-404f-86ad-33693f544948}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{73f6dd5b-437e-11d3-b88e-00c04f79f802}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/><ViewBookmark Name=\"ST:0:0:{cdbdee54-b399-484b-b763-db2c3393d646}\" ViewBookmarkType=\"Raft\" DockedHeight=\"185\" DockedWidth=\"707\" FloatingHeight=\"192\" FloatingWidth=\"719\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"3fa16f6c-d3ac-46db-8659-5683046e37be\" FloatingTop=\"466\" FloatingLeft=\"978\" FloatingHeight=\"192\" FloatingWidth=\"1450\"><TabGroup DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"><ViewBookmark Name=\"ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{637792aa-f332-4bb5-be6c-066b0e88eced}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{be4d7042-ba3f-11d2-840e-00c04f9902c1}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{588470cc-84f8-4a57-9ac4-86bca0625ff4}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{5415ea3a-d813-4948-b51e-562082ce0887}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f564}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f565}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{905da7d1-18fd-4a46-8d0f-a5ff58ada9de}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{519e8a32-1c95-4a42-956f-2cee2f28eb0f}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{92547016-2bd0-4dfe-bd4f-5b52bdce0037}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{b869198c-f673-46d2-83ae-64f515277716}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{b8399b49-7330-487b-9235-7d2e969d0a79}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/><ViewBookmark Name=\"ST:0:0:{778b5376-ad77-4751-acdc-f3d18343f8dd}\" ViewBookmarkType=\"Raft\" DockedHeight=\"178.255208333333\" DockedWidth=\"1438\" FloatingHeight=\"192\" FloatingWidth=\"1450\"/></TabGroup></wm:WMFloatSite><wm:WMFloatSite Id=\"257728c7-9c44-467c-9156-4f4fdc5ded47\" FloatingTop=\"635\" FloatingLeft=\"562\" FloatingHeight=\"341\" FloatingWidth=\"721\"><ViewBookmark Name=\"ST:0:0:{28836128-fc2c-11d2-a433-00c04f72d18a}\" ViewBookmarkType=\"Raft\" DockedHeight=\"334\" DockedWidth=\"709\" FloatingHeight=\"341\" FloatingWidth=\"721\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"cff8998e-c277-4ffc-a830-95472b0a3fbc\" FloatingTop=\"529\" FloatingLeft=\"796\" FloatingHeight=\"341\" FloatingWidth=\"721\"><ViewBookmark Name=\"ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}\" ViewBookmarkType=\"Raft\" FloatingHeight=\"341\" FloatingWidth=\"721\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"27f77128-c8d9-4f15-bab1-e72789a23128\" FloatingTop=\"953\" FloatingLeft=\"193\" FloatingHeight=\"148\" FloatingWidth=\"1450\"><ViewBookmark Name=\"ST:0:0:{e62ce6a0-b439-11d0-a79d-00a0c9110051}\" ViewBookmarkType=\"Raft\" DockedHeight=\"141\" DockedWidth=\"720.983448275862\" FloatingHeight=\"148\" FloatingWidth=\"1450\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"7e836a22-3cf8-45e6-8ce2-2df337e3cf55\" FloatingTop=\"990\" FloatingLeft=\"572\" FloatingHeight=\"197\" FloatingWidth=\"1686\"><ViewBookmark Name=\"ST:0:0:{68487888-204a-11d3-87eb-00c04f7971a5}\" ViewBookmarkType=\"Raft\" FloatingHeight=\"197\" FloatingWidth=\"1686\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"c152ff2b-fbee-42d9-b36e-462d383ecd13\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"520\" FloatingWidth=\"550\"><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{6324226f-61b6-4f28-92ee-18d4b5fe1e48}\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"520\" FloatingWidth=\"550\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"145905ff-042c-4e86-9c61-f6e0a1052fa6\"><TabGroup><wm:ToolWindowView Name=\"ST:1:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/><wm:ToolWindowView Name=\"ST:2:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/><wm:ToolWindowView Name=\"ST:0:0:{904ea1b4-c6a7-4eca-94a1-76eab06c5187}\"/></TabGroup></wm:WMFloatSite></WindowProfile></debug><NoToolWin><WindowProfile xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:wm=\"clr-namespace:Microsoft.VisualStudio.Platform.WindowManagement;assembly=Microsoft.VisualStudio.Platform.WindowManagement\" Name=\"NoToolWin\" xmlns=\"clr-namespace:Microsoft.VisualStudio.PlatformUI.Shell;assembly=Microsoft.VisualStudio.Shell.ViewManager\"><MainSite FloatingHeight=\"774\" FloatingWidth=\"1296\"><AutoHideRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\" DockRestriction=\"AlwaysFloating\"><AutoHideChannel Orientation=\"Vertical\"><AutoHideGroup><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{25f7e850-ffa1-11d0-b63f-00a0c922e851}\" DockedHeight=\"768\" DockedWidth=\"204.8\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{873151d0-cf2e-48cc-b4bf-ad0394f6a3c3}\" DockedHeight=\"768\" DockedWidth=\"204.8\" FloatingHeight=\"537.6\" FloatingWidth=\"256\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Right\" Orientation=\"Vertical\"><AutoHideGroup><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingTop=\"76.8\" FloatingLeft=\"819.2\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/></AutoHideGroup></AutoHideChannel><AutoHideChannel Dock=\"Top\"/><AutoHideChannel Dock=\"Bottom\"/><DockRoot IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><DockGroup Orientation=\"Vertical\" DockedHeight=\"768\" DockedWidth=\"174.08\"><TabGroup DockedHeight=\"652.8\" DockedWidth=\"174.08\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{74946827-37a0-11d2-a273-00c04f8ef4ff}\" DockedHeight=\"652.8\" DockedWidth=\"174.08\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{099ca9ea-0ae4-4e31-a7e4-fe09bd1715cc}\" DockedHeight=\"652.8\" DockedWidth=\"174.08\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}\" DockedHeight=\"652.8\" DockedWidth=\"174.08\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView Name=\"ST:0:0:{9f3ec988-1174-4746-a66a-3969715d1fc7}\" DockedHeight=\"652.8\" DockedWidth=\"174.08\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5b6781c0-e99d-11d0-9954-00a0c91bc8e5}\" DockedHeight=\"652.8\" DockedWidth=\"174.08\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/></TabGroup><TabGroup DockedHeight=\"768\" DockedWidth=\"174.08\"><ViewBookmark Name=\"ST:0:0:{25f7e850-ffa1-11d0-b63f-00a0c922e851}\" DockedHeight=\"768\" DockedWidth=\"174.08\"/><ViewBookmark Name=\"ST:0:0:{873151d0-cf2e-48cc-b4bf-ad0394f6a3c3}\" DockedHeight=\"768\" DockedWidth=\"174.08\"/></TabGroup></DockGroup><DockGroup Orientation=\"Vertical\" IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><TabGroup DockedHeight=\"153.6\" DockedWidth=\"819.2\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{350f9856-a72b-11d2-8ad0-00c04f79e479}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ca4b8ff5-bfc7-11d2-9929-00c04f68fdaf}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{51c76317-9037-4cf2-a20a-6206fd30b4a1}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e62ce6a0-b439-11d0-a79d-00a0c9110051}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{8d263989-ff4b-4a78-90c8-b2ba3fa69311}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"199.68\" FloatingWidth=\"512\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{37aba9be-445a-11d3-9949-00c04f68fd0a}\" DockedHeight=\"153.6\" DockedWidth=\"819.2\" FloatingHeight=\"153.6\" FloatingWidth=\"614.4\"/></TabGroup><wm:WMDocumentGroupContainer IsVisible=\"True\" DockedHeight=\"*\" DockedWidth=\"*\"><wm:WMDocumentGroup><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{387cb18d-6153-4156-9257-9ac3f9207bbe}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c79b74ff-f1d7-4c94-aefa-4d22bfe1b1f9}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{46c87f81-5a06-43a8-9e25-85d33bac49f8}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}\" FloatingHeight=\"384\" FloatingWidth=\"512\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{99b8fa2f-ab90-4f57-9c32-949f146f1914}\" FloatingHeight=\"537.6\" FloatingWidth=\"716.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf577b8c-4134-11d2-83e5-00c04f9902c1}\" FloatingHeight=\"384\" FloatingWidth=\"358.4\"/></wm:WMDocumentGroup></wm:WMDocumentGroupContainer><DockGroup IsVisible=\"True\" DockedHeight=\"192\" DockedWidth=\"512\"><TabGroup DockedHeight=\"192\" DockedWidth=\"512\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{f2e84780-2af1-11d1-a7fa-00a0c9110051}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{4a18f9d0-b838-11d0-93eb-00a0c90f2734}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:1:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:2:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:3:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:4:0:{90243340-bd7a-11d0-93ef-00a0c90f2734}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887920-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{0f887921-c2b6-11d2-9375-0080c747d9a0}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{68487888-204a-11d3-87eb-00c04f7971a5}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{fdffccf2-5f63-404f-86ad-33693f544948}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd5b-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{cdbdee54-b399-484b-b763-db2c3393d646}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/></TabGroup><TabGroup DockedHeight=\"192\" DockedWidth=\"100\"><ViewBookmark Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"192\" DockedWidth=\"100\"/><ViewBookmark Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"192\" DockedWidth=\"100\"/></TabGroup><TabGroup IsVisible=\"True\" DockedHeight=\"192\" DockedWidth=\"512\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{be4d7042-ba3f-11d2-840e-00c04f9902c1}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{28836128-fc2c-11d2-a433-00c04f72d18a}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{588470cc-84f8-4a57-9ac4-86bca0625ff4}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{5415ea3a-d813-4948-b51e-562082ce0887}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{a0c5197d-0ac7-4b63-97cd-8872a789d233}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f564}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"DontForceCreate\" Name=\"ST:0:0:{2456bd12-ecf7-4988-a4a6-67d49173f565}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{905da7d1-18fd-4a46-8d0f-a5ff58ada9de}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{519e8a32-1c95-4a42-956f-2cee2f28eb0f}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{92547016-2bd0-4dfe-bd4f-5b52bdce0037}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"614.4\"/><wm:ToolWindowView Name=\"ST:0:0:{b869198c-f673-46d2-83ae-64f515277716}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView Name=\"ST:0:0:{b8399b49-7330-487b-9235-7d2e969d0a79}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView Name=\"ST:0:0:{778b5376-ad77-4751-acdc-f3d18343f8dd}\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingHeight=\"192\" FloatingWidth=\"460.8\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e9f0ab1f-5649-4620-a7d9-ee96788df550}\" IsSelected=\"True\" IsVisible=\"True\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/><wm:ToolWindowView ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{271d5c74-3ac9-4328-910a-713e65f6ec25}\" IsVisible=\"True\" DockedHeight=\"192\" DockedWidth=\"512\" FloatingTop=\"100\" FloatingLeft=\"100\" FloatingHeight=\"450\" FloatingWidth=\"450\"/></TabGroup></DockGroup></DockGroup><TabGroup DockedHeight=\"652.8\" DockedWidth=\"348.16\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{ed485b08-5acf-4ce9-8e13-699174ea0201}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1c64b9c2-e352-428e-a56d-0ace190b99a6}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{131369f2-062d-44a2-8671-91ff31efb4f4}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"348.16\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{c9c0ae26-aa77-11d2-b3f0-0000f87570ee}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{de1fc918-f32e-4dd7-a915-1792a051f26b}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{eefa5220-e298-11d0-8f78-00a0c9110057}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{ecdd9ee0-ac6b-11d0-89f9-00a0c9110055}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{2d7728c2-de0a-45b5-99aa-89b609dfde73}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{3addf8e2-81cc-41a0-9785-dbd2d86064bd}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{0db31cc8-2322-4f59-a610-1fdc8423df77}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{66dba47c-61df-11d2-aa79-00c04f990343}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{4a791147-19e4-11d3-b86b-00c04f79f802}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{73f6dd58-437e-11d3-b88e-00c04f79f802}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{57dc5d59-11c2-4955-a7b4-d7699d677e93}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{07cd18b4-3ba1-11d2-890a-0060083196c6}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{1cba9826-3184-4799-a184-784e41b56398}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{38ed9834-0c97-445b-bd1d-f78f3e08afac}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{402dc223-d700-4029-866f-acee803f3f0c}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{dd1ddd20-d59b-11da-a94d-0800200c9a66}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"537.6\" FloatingWidth=\"204.8\"/><wm:ToolWindowView ContextVisibilityEnabled=\"False\" Name=\"ST:0:0:{a34b1c5d-6d37-4a0c-a8b0-99f8e8158b48}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\" FloatingHeight=\"384\" FloatingWidth=\"204.8\"/><ViewBookmark Name=\"ST:0:0:{c93a910a-0fa6-4307-93a4-f2bd61ec7828}\" DockedHeight=\"652.8\" DockedWidth=\"348.16\"/></TabGroup></DockGroup></DockRoot></AutoHideRoot></MainSite><wm:WMFloatSite Id=\"adb8240f-84cc-4455-b9b3-8371bfc1680c\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{53024d34-0ef5-11d3-87e0-00c04f7971a5}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"98272bb3-9647-4ac4-9043-f436e94637c5\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{e830ec50-c2b5-11d2-9375-0080c747d9a0}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"76.8\" FloatingWidth=\"337.92\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"087173d9-a0b6-4d85-8953-c4dd284d6e7c\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{cf2ddc32-8cad-11d2-9302-005345000000}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"222.72\" FloatingWidth=\"378.88\"/></wm:WMFloatSite><wm:WMFloatSite Id=\"45d07a35-c62c-4934-977e-73c6a032ecda\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"><wm:ToolWindowView ContextVisibilityEnabled=\"False\" ForceCreate=\"ForceCreate\" Name=\"ST:0:0:{58875c41-862b-4d6f-b046-03e8a333907e}\" DockedHeight=\"100\" DockedWidth=\"100\" FloatingHeight=\"215.04\" FloatingWidth=\"675.84\"/></wm:WMFloatSite></WindowProfile></NoToolWin></Category></Category><Category name=\"Browser Link_BrowserLinkOptions\" Category=\"{ecf15adb-ab46-38d6-9d69-060f7552fb26}\" Package=\"{8c28e535-abc9-4f92-b5c9-6c16617c8884}\" RegisteredName=\"Browser Link_BrowserLinkOptions\" PackageName=\"EurekaPackage\">\n\t\t<PropertyValue name=\"AutoSyncEnabled\">True</PropertyValue>\n\t\t<PropertyValue name=\"BrowserLinkEnabled\">False</PropertyValue>\n\t</Category><Category name=\"CSS_Advanced\" Category=\"{978df9f7-5f7c-46c1-afe6-3e4335e93514}\" Package=\"{5330c67f-305c-4b17-a3dc-fbb7aee69c09}\" RegisteredName=\"CSS_Advanced\" PackageName=\"CssPackage\"/><Category name=\"CodeLens\" Category=\"{04DA95C1-B198-491D-BB93-DAD8610F3019}\" Package=\"{E269B994-EF71-4CE0-8BCD-581C217372E8}\" RegisteredName=\"CodeLens\" PackageName=\"Microsoft.VisualStudio.Editor.Implementation.EditorPackage\"><PropertyValue name=\"IsCodeLensEnabled\">true</PropertyValue><CodeLensDisabledProviders/></Category><Category name=\"Debugger\" Category=\"{EEDBF29A-5C8B-4E01-827C-263382C18CFE}\" Package=\"{C9DD4A57-47FB-11D2-83E7-00C04F9902C1}\" RegisteredName=\"Debugger\" PackageName=\"Visual Studio Debugger\"><PropertyValue name=\"ConfirmDeleteAllBreakpoints\">1</PropertyValue><PropertyValue name=\"StopAllProcesses\">1</PropertyValue><PropertyValue name=\"StopOnExceptionCrossingManagedBoundary\">0</PropertyValue><PropertyValue name=\"EnableAddressLevelDebugging\">1</PropertyValue><PropertyValue name=\"ShowDisassemblyWhenNoSource\">0</PropertyValue><PropertyValue name=\"EnableBreakpointConstraints\">1</PropertyValue><PropertyValue name=\"UseExceptionHelper\">1</PropertyValue><PropertyValue name=\"AutoUnwindOnException\">1</PropertyValue><PropertyValue name=\"JustMyCode\">1</PropertyValue><PropertyValue name=\"ShowNonPublicMembers\">0</PropertyValue><PropertyValue name=\"WarnIfNoUserCodeOnLaunch\">1</PropertyValue><PropertyValue name=\"FrameworkSourceStepping\">0</PropertyValue><PropertyValue name=\"WarnAboutSymbolCacheDuringRemoteManagedDebugging\">1</PropertyValue><PropertyValue name=\"EnableStepFiltering\">1</PropertyValue><PropertyValue name=\"WarnOnStepFilter\">1</PropertyValue><PropertyValue name=\"AllowImplicitFuncEval\">1</PropertyValue><PropertyValue name=\"AllowToString\">1</PropertyValue><PropertyValue name=\"UseSourceServer\">0</PropertyValue><PropertyValue name=\"ShowSourceServerDiagnostics\">0</PropertyValue><PropertyValue name=\"AlwaysColorMarkerText\">0</PropertyValue><PropertyValue name=\"UseDocumentChecksum\">1</PropertyValue><PropertyValue name=\"OutputToImmediate\">0</PropertyValue><PropertyValue name=\"ShowRawStructures\">0</PropertyValue><PropertyValue name=\"DisableJITOptimization\">0</PropertyValue><PropertyValue name=\"ShowNoSymbolsDialog\">1</PropertyValue><PropertyValue name=\"ShowScriptDebuggingDisabledDialog\">0</PropertyValue><PropertyValue name=\"EnableILInterpreterMinidump\">1</PropertyValue><PropertyValue name=\"EnableILInterpreterLive\">0</PropertyValue><PropertyValue name=\"HexDisplay\">1</PropertyValue><PropertyValue name=\"HexInput\">0</PropertyValue><PropertyValue name=\"MapClientBreakpoints\">1</PropertyValue><PropertyValue name=\"AddUnmappedBreakpointAtMappedLocation\">0</PropertyValue><PropertyValue name=\"StepIntoOnRestart\">0</PropertyValue><PropertyValue name=\"ENCEnable\">1</PropertyValue><PropertyValue name=\"ENCApplyChangesOnContinue\">1</PropertyValue><PropertyValue name=\"ENCWelcome\">0</PropertyValue><PropertyValue name=\"ENCStaleCodeWarning\">1</PropertyValue><PropertyValue name=\"ENCPrecompile\">0</PropertyValue><PropertyValue name=\"ENCRelink\">1</PropertyValue><PropertyValue name=\"NOENCAllowEdits\">1</PropertyValue><PropertyValue name=\"NOENCIgnore\">0</PropertyValue><PropertyValue name=\"NOENCRebuild\">0</PropertyValue><PropertyValue name=\"ENCEnableWhenRemoteDebugging\">0</PropertyValue><PropertyValue name=\"ConfirmFoundFiles\">0</PropertyValue><PropertyValue name=\"DisasmLineNumbers\">0</PropertyValue><PropertyValue name=\"ModulesShowAll\">0</PropertyValue><PropertyValue name=\"UseCodeSense\">1</PropertyValue><PropertyValue name=\"DisasmFields\">67577</PropertyValue><PropertyValue name=\"CallStackViewOptions\">3604736</PropertyValue><PropertyValue name=\"ShowExternalCode\">0</PropertyValue><PropertyValue name=\"SourceStepUnit\">0</PropertyValue><PropertyValue name=\"DisasmStepUnit\">2</PropertyValue><PropertyValue name=\"CrossThreadCallStack\">1</PropertyValue><PropertyValue name=\"ShowOnlyFlaggedThreads\">0</PropertyValue><PropertyValue name=\"ConcurrencySupport\">1</PropertyValue><PropertyValue name=\"GuessAtThreadNames\">1</PropertyValue><PropertyValue name=\"ShowOtherThreadIpMarkers\">0</PropertyValue><PropertyValue name=\"ShowThreadCategory\">1</PropertyValue><PropertyValue name=\"SaveRemoteDumps\">1</PropertyValue><PropertyValue name=\"LongEvalTimeout\">10000</PropertyValue><PropertyValue name=\"NormalEvalTimeout\">5000</PropertyValue><PropertyValue name=\"QuickwatchTimeout\">15000</PropertyValue><PropertyValue name=\"DataTipTimeout\">1500</PropertyValue><PropertyValue name=\"AutosReturnValsTimeout\">1000</PropertyValue><PropertyValue name=\"AutosRegistersTimeout\">1000</PropertyValue><PropertyValue name=\"LocalsTimeout\">1000</PropertyValue><PropertyValue name=\"RegistersTimeout\">2000</PropertyValue><PropertyValue name=\"AddressExpressionTimeout\">2000</PropertyValue><PropertyValue name=\"ScriptDocsTimeout\">5000</PropertyValue><PropertyValue name=\"ImmediateWindowTimeout\">10000</PropertyValue><PropertyValue name=\"SetValueTimeout\">10000</PropertyValue><PropertyValue name=\"TreeGridRePaintTimer\">250</PropertyValue><PropertyValue name=\"TreeGridBusyPaintTimer\">1000</PropertyValue><PropertyValue name=\"ModulesWindowUpdateTimer\">500</PropertyValue><PropertyValue name=\"TreeGridDelayOnEnterTimer\">125</PropertyValue><PropertyValue name=\"DelayTreeGridPaintOnBreak\">1</PropertyValue><PropertyValue name=\"ShowNonprintableCharsAsGlyphs\">0</PropertyValue><PropertyValue name=\"ShowSystemProcesses\">0</PropertyValue><PropertyValue name=\"ShowProcessesFromAllSessions\">0</PropertyValue><PropertyValue name=\"EnhancedDataTips\">1</PropertyValue><PropertyValue name=\"DataTipDismissalSensitivity\">1</PropertyValue><PropertyValue name=\"DataTipKeyboardDismissal\">0</PropertyValue><PropertyValue name=\"UserSpecifiedEngines\">0</PropertyValue><PropertyValue name=\"OutputOnException\">1</PropertyValue><PropertyValue name=\"OutputOnStepFilter\">1</PropertyValue><PropertyValue name=\"OutputOnModuleLoad\">0</PropertyValue><PropertyValue name=\"OutputOnModuleUnload\">0</PropertyValue><PropertyValue name=\"OutputOnModuleSymbolSearch\">1</PropertyValue><PropertyValue name=\"OutputOnProcessDestroy\">1</PropertyValue><PropertyValue name=\"OutputOnThreadDestroy\">0</PropertyValue><PropertyValue name=\"OutputOnOutputDebugString\">1</PropertyValue><PropertyValue name=\"OutputOnDebuggerMessage\">1</PropertyValue><PropertyValue name=\"VariableWindowIcons\">1</PropertyValue><PropertyValue name=\"DisableAttachSecurityWarning\">0</PropertyValue><PropertyValue name=\"LoadDllExports\">0</PropertyValue><PropertyValue name=\"NativeRPC\">0</PropertyValue><PropertyValue name=\"AllowSideEffectEval\">0</PropertyValue><PropertyValue name=\"LoadSymbolsWhenSettingsChanged\">1</PropertyValue><PropertyValue name=\"SymbolUseExcludeList\">1</PropertyValue><PropertyValue name=\"SymbolsAlwaysLoadAdjacent\">1</PropertyValue><PropertyValue name=\"DontForceCacheOnManualLoad\">0</PropertyValue><PropertyValue name=\"SymbolUseMSSymbolServers\">0</PropertyValue><PropertyValue name=\"AutoLoadFromSymbolPath\">1</PropertyValue><PropertyValue name=\"OneClickEdit\">0</PropertyValue><PropertyValue name=\"OfferArrayExpansion\">0</PropertyValue><PropertyValue name=\"VariableWindowPromptOnLargeExpansion\">1</PropertyValue><PropertyValue name=\"VariableWindowMaxSupportedChildren\">1000000</PropertyValue><PropertyValue name=\"PromptToAddSourceToIgnoreList\">0</PropertyValue><PropertyValue name=\"OwnerDrawDebugLocationToolbar\">0</PropertyValue><PropertyValue name=\"EnableSelectedBreakpointHighlight\">1</PropertyValue><PropertyValue name=\"DisableUniscribe\">0</PropertyValue><PropertyValue name=\"ShowParallelStacksBottomUp\">1</PropertyValue><PropertyValue name=\"ThreadsWindowGroupingColumn\">2048</PropertyValue><PropertyValue name=\"ThreadsWindowVisibleColumns\">253</PropertyValue><PropertyValue name=\"ThreadsWindowShowTipWhenCallStackExpanded\">0</PropertyValue><PropertyValue name=\"AllowDesignModePinning\">0</PropertyValue><PropertyValue name=\"CallStackParamToString\">0</PropertyValue><PropertyValue name=\"CheckRDTForFilesFirst\">1</PropertyValue><PropertyValue name=\"AllowSourceServerInPartialTrust\">0</PropertyValue><PropertyValue name=\"ENCEnableCrossUser\">0</PropertyValue><PropertyValue name=\"ENCEnableNative\">1</PropertyValue><PropertyValue name=\"OpenNewFilesProvisionally\">1</PropertyValue><PropertyValue name=\"ForceRealFuncEval\">1</PropertyValue><PropertyValue name=\"ShowOnlyCurrentProcess\">0</PropertyValue><PropertyValue name=\"GpuRaceHazardsAllowSame\">0</PropertyValue><PropertyValue name=\"DisasmToolbarIsExpanded\">0</PropertyValue><PropertyValue name=\"AlwaysRunUntrustedSourceServerCommands\">0</PropertyValue><PropertyValue name=\"DebugEngineCompatibilityMode\">0</PropertyValue><PropertyValue name=\"CallStackViewOptionsEx\">1</PropertyValue><PropertyValue name=\"ManagedDebugEngineCompatibilityMode_Normal\">0</PropertyValue><PropertyValue name=\"ManagedDebugEngineCompatibilityMode_ProductInstalled\">1</PropertyValue><PropertyValue name=\"ManagedMemoryViewCollapseSmallObjects\">0</PropertyValue><PropertyValue name=\"NativeDebugEngineCompatibilityMode\">0</PropertyValue><PropertyValue name=\"ManagedMemoryViewHideUndeterminedTypes\">1</PropertyValue><PropertyValue name=\"ManagedMemoryViewJustMyCode\">1</PropertyValue><PropertyValue name=\"UseLegacyManagedEE\">0</PropertyValue><PropertyValue name=\"ShowClrCustomVisualizerSecurityWarning\">1</PropertyValue><PropertyValue name=\"EnableNativeDebugHeap\">0</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnostics\">1</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsPreviewSelection\">0</PropertyValue><PropertyValue name=\"EnableDiagnosticToolsWindow\">1</PropertyValue><PropertyValue name=\"EnableIntelliTrace\">1</PropertyValue><PropertyValue name=\"PerformanceTipsEnableSysTime\">0</PropertyValue><PropertyValue name=\"TextVisualizerStringLimit\">32768</PropertyValue><PropertyValue name=\"ExceptionDescriptionMessageLimit\">8192</PropertyValue><PropertyValue name=\"UseNewExceptionHelper\">0</PropertyValue><PropertyValue name=\"UseSourceLink\">1</PropertyValue><PropertyValue name=\"EnableAspNetJavaScriptDebuggingOnLaunch\">0</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsInAppMenu\">1</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsEditAndContinue\">1</PropertyValue><PropertyValue name=\"EnableIntelliTraceInstrumentation\">0</PropertyValue><PropertyValue name=\"EnableRunToClick\">1</PropertyValue><PropertyValue name=\"EnableEdgeDevToolsWWAOnLaunch\">0</PropertyValue><PropertyValue name=\"EnableLegacyChromeJavaScriptDebuggerForASPNET\">0</PropertyValue><PropertyValue name=\"UseGcmSourceLinkAuthFallback\">0</PropertyValue><PropertyValue name=\"EnableLaunchChromeUnelevatedExperiment\">0</PropertyValue><PropertyValue name=\"CallStackShowFrameType\">0</PropertyValue><PropertyValue name=\"UseVBCSLegacyExpressionEvaluator\">0</PropertyValue><PropertyValue name=\"EnableIntelliTraceSnapshot\">0</PropertyValue><PropertyValue name=\"PromptIntelliTraceSnapshot\">1</PropertyValue><PropertyValue name=\"EnableIntelliTraceSnapshotException\">1</PropertyValue><PropertyValue name=\"MaxIntelliTraceSnapshotOnException\">5</PropertyValue><PropertyValue name=\"DisableForegroundWindowSwitching\">0</PropertyValue><PropertyValue name=\"WarnIfNoAspNetJavaScriptDebuggingOnLaunch\">1</PropertyValue><PropertyValue name=\"DisableIntegratedConsole\">0</PropertyValue><PropertyValue name=\"EESearchAllItems\">1</PropertyValue><PropertyValue name=\"EnableJavaScriptMultitargetDebugging\">1</PropertyValue><PropertyValue name=\"EnableJavaScriptMultitargetNodeDebug\">1</PropertyValue><PropertyValue name=\"SymbolUseNugetSymbolServers\">0</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsJustMyXaml\">1</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsInAppSelectionAutoOff\">1</PropertyValue><PropertyValue name=\"EnableXamlVisualDiagnosticsHotReloadOnSave\">0</PropertyValue><PropertyValue name=\"EEDefaultSearchDepth\">3</PropertyValue><PropertyValue name=\"AllowOutOfProcessSymbols\">1</PropertyValue><PropertyValue name=\"EEMaxSearchDepth\">10</PropertyValue><PropertyValue name=\"ShowEESearchNoResultsDialog\">1</PropertyValue><PropertyValue name=\"EnableForegroundWindowSwitching\">1</PropertyValue><PropertyValue name=\"EnableFastEvaluate\">1</PropertyValue><PropertyValue name=\"FilterToFavorites\">0</PropertyValue><PropertyValue name=\"UseShortDisplayString\">0</PropertyValue><PropertyValue name=\"ScriptDocumentsNodeExpanded\">0</PropertyValue><PropertyValue name=\"ShowParallelStacksSearchNoResultsDialog\">1</PropertyValue><PropertyValue name=\"AutomaticSymbolLoad\">1</PropertyValue><PropertyValue name=\"AttachToProcessesDialog_ShowParentChildRelationships\">0</PropertyValue><PropertyValue name=\"AttachToProcessesDialog_AutoRefresh\">1</PropertyValue><PropertyValue name=\"EnableExternalSourcesNode\">1</PropertyValue><PropertyValue name=\"EnableNetHotReloadWhenNoDebugging\">1</PropertyValue><PropertyValue name=\"EnableNetHotReloadOnFileSave\">0</PropertyValue><PropertyValue name=\"EnableStickyDataTips\">0</PropertyValue><PropertyValue name=\"UseVSDebugConsole\">1</PropertyValue><PropertyValue name=\"HotReloadVerbosityLevel\">0</PropertyValue><PropertyValue name=\"DisablePrecompiledImages\">0</PropertyValue><PropertyValue name=\"StringVisualizerWordWrap\">1</PropertyValue><PropertyValue name=\"SymbolIgnoreNetworkDriveDebugDirectory\">1</PropertyValue><PropertyValue name=\"EnableAutomaticDecompilation\">1</PropertyValue><PropertyValue name=\"NatvisRecursionLimit\">100</PropertyValue><PropertyValue name=\"NatvisComplexityLimit\">100</PropertyValue><PropertyValue name=\"HighlightCurrentStatementAndStatementsWithBreakpoints\">1</PropertyValue><PropertyValue name=\"EnableAutomaticDeoptimization\">1</PropertyValue><PropertyValue name=\"SourceServerExtractToDirectory\"/><PropertyValue name=\"FrameworkSourceServerName\">https://referencesource.microsoft.com/symbols</PropertyValue><PropertyValue name=\"PublicSymbolServerName\"/><PropertyValue name=\"PublicSymbolServerName2\">https://msdl.microsoft.com/download/symbols</PropertyValue><PropertyValue name=\"DefaultTracepointMessage\">Function: $FUNCTION, Thread: $TID $TNAME</PropertyValue><PropertyValue name=\"ProgramToDebugPath\"/><PropertyValue name=\"AttachToProcessDefaultEngineList\"/><PropertyValue name=\"SecureSourceLocalDirectory\"/><PropertyValue name=\"SymbolPath\"/><PropertyValue name=\"SymbolPathState\"/><PropertyValue name=\"SymbolCacheDir\"/><PropertyValue name=\"SymbolIncludeList\"/><PropertyValue name=\"SymbolIncludeListState\"/><PropertyValue name=\"SymbolExcludeList\"/><PropertyValue name=\"SymbolExcludeListState\"/><PropertyValue name=\"SourceLinkExtractToDirectory\"/><PropertyValue name=\"NugetSymbolServerName\">https://symbols.nuget.org/download/symbols</PropertyValue><PropertyValue name=\"ThreadsWindowColumnsOrderList\"/></Category><Category name=\"Git Version Control_GitSccProvider\" Category=\"{33a4cda9-b7a6-3f4f-9e1f-e4d71f0a9cfa}\" Package=\"{7fe30a77-37f9-4cf2-83dd-96b207028e1b}\" RegisteredName=\"Git Version Control_GitSccProvider\" PackageName=\"SccProviderPackage\"><PropertyValue name=\"HistoryGraphHighlightHeadNodeDiameter\">8</PropertyValue></Category><Category name=\"HTML_Advanced\" Category=\"{3d9c2439-5c0a-4592-b9e8-42e26dfe636b}\" Package=\"{cf49ec7d-92b1-4bbd-9254-9cc13978e82e}\" RegisteredName=\"HTML_Advanced\" PackageName=\"HtmlPackage\"/><Category name=\"JSON_Advanced\" Category=\"{78c48687-be97-424b-a3fa-378de0dac07c}\" Package=\"{a6efef5f-be9b-432a-adfe-74a119ab4478}\" RegisteredName=\"JSON_Advanced\" PackageName=\"JsonPackage\"/><Category name=\"JSON_Schema\" Category=\"{A274BD6E-74E6-4698-B631-1690B1E514BB}\" Package=\"{a6efef5f-be9b-432a-adfe-74a119ab4478}\" RegisteredName=\"JSON_Schema\" PackageName=\"JsonPackage\"/><Category name=\"LESS_Advanced\" Category=\"{1b200b66-f69e-452e-9c6f-d38f9cb0628b}\" Package=\"{f4b61ec8-36d8-4155-b2a0-199587d8ddcc}\" RegisteredName=\"LESS_Advanced\" PackageName=\"LessPackage\"/><Category name=\"Projects_Web Package Management_External Web Tools\" Category=\"{b521100c-f698-4018-b1cf-6421fc59fa9b}\" Package=\"{cb03d63d-47be-437d-b26b-1ad8aa7ff394}\" RegisteredName=\"Projects_Web Package Management_External Web Tools\" PackageName=\"PackageManagementPackage\">\n\t\t<PropertyValue name=\"ToolPaths\">.\\node_modules\\.bin;$(DevEnvDir)\\Extensions\\Microsoft\\Web Tools\\External;$(PATH);$(DevEnvDir)\\Extensions\\Microsoft\\Web Tools\\External\\git</PropertyValue>\n\t</Category><Category name=\"Projects_Web Package Management_Package Restore\" Category=\"{03D27F49-12E4-4282-94F3-03A8AEF90B8F}\" Package=\"{cb03d63d-47be-437d-b26b-1ad8aa7ff394}\" RegisteredName=\"Projects_Web Package Management_Package Restore\" PackageName=\"PackageManagementPackage\">\n\t\t<PropertyValue name=\"NpmRestoreOnOpen\">True</PropertyValue>\n\t\t<PropertyValue name=\"NpmRestoreOnSave\">True</PropertyValue>\n\t</Category><Category name=\"SCSS_Advanced\" Category=\"{cea4fb93-f562-4d14-980f-8d9cab6e0a71}\" Package=\"{60116bb0-6f13-49e2-a1ce-a185c783b68b}\" RegisteredName=\"SCSS_Advanced\" PackageName=\"ScssPackage\"/><Category name=\"Source Control_TeamFoundation\" Category=\"{2A718788-A6D9-44C5-90EF-438BF5B06A74}\" Package=\"{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}\" RegisteredName=\"Source Control_TeamFoundation\" PackageName=\"Microsoft.VisualStudio.TeamFoundation.VersionControl.HatPackage\">\n\t\t<PropertyValue name=\"AttemptToAutoResolveConflicts\">true</PropertyValue>\n\t\t<PropertyValue name=\"GetLatestOnCheckOut\">false</PropertyValue>\n\t\t<PropertyValue name=\"IsProxyEnabled\">False</PropertyValue>\n\t\t<PropertyValue name=\"OpenSceToMostRecentPath\">false</PropertyValue>\n\t\t<PropertyValue name=\"PromptToConfirmMergeResult\">-1</PropertyValue>\n\t\t<PropertyValue name=\"ProxyUrl\"/>\n\t\t<PropertyValue name=\"ShowDeletedItems\">false</PropertyValue>\n\t</Category><Category name=\"Test Tools_Test Execution\" Category=\"{48d11193-265d-1458-743e-2b88c655b377}\" Package=\"{a9405ae6-9ac6-4f0e-a03f-7afe45f6fcb7}\" RegisteredName=\"Test Tools_Test Execution\" PackageName=\"Microsoft.VisualStudio.TestTools.TestCaseManagement.QualityToolsPackage, Microsoft.VisualStudio.QualityTools.TestCaseManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n\t\t<PropertyValue name=\"IsCodeColoringEnabled\">True</PropertyValue>\n\t\t<PropertyValue name=\"IsInPlaceInstrumentationEnabled\">True</PropertyValue>\n\t\t<PropertyValue name=\"KeepHostProcessAlive\">True</PropertyValue>\n\t\t<PropertyValue name=\"MaxTestRunsPerSolution\">25</PropertyValue>\n\t\t<PropertyValue name=\"PathToWebRoot\"/>\n\t\t<PropertyValue name=\"UnitTestResultNavigatePointOfFailure\">False</PropertyValue>\n\t</Category><Category name=\"Text Editor_Advanced\" Category=\"{efd55f3d-23c1-4a1d-a58b-77295611c7d4}\" Package=\"{e269b994-ef71-4ce0-8bcd-581c217372e8}\" RegisteredName=\"Text Editor_Advanced\" PackageName=\"Microsoft.VisualStudio.Editor.Implementation.EditorPackage\"><PropertyValue name=\"Diff/View/HighlightMode\">1</PropertyValue><PropertyValue name=\"Diff/View/ShowDiffOverviewMargin\">false</PropertyValue><PropertyValue name=\"ResponsiveCompletion\">true</PropertyValue><PropertyValue name=\"ChangeMargin/InteractiveGesture\">1</PropertyValue><PropertyValue name=\"WordBasedSuggestions\">true</PropertyValue><PropertyValue name=\"DefaultCompletionMode\">0</PropertyValue><PropertyValue name=\"DisplaceCaretLineOption\">true</PropertyValue><PropertyValue name=\"UseBoxSelectionOption\">false</PropertyValue><PropertyValue name=\"AdaptiveFormatting\">true</PropertyValue><PropertyValue name=\"EnableIndexedFindInFiles\">true</PropertyValue><PropertyValue name=\"EnableRtfCopy\">true</PropertyValue><PropertyValue name=\"MaxRtfCopyLength\">10240</PropertyValue><PropertyValue name=\"EnableTypingLatencyGuard\">true</PropertyValue><PropertyValue name=\"UserCustomMaximumTypingLatency\">-1</PropertyValue><PropertyValue name=\"EditorVerticalScrollSensitivity\">3</PropertyValue><PropertyValue name=\"EditorHorizontalScrollSensitivity\">1</PropertyValue><PropertyValue name=\"TextView/TextFormattingMode\">0</PropertyValue><PropertyValue name=\"TextView/TextRenderingMode\">0</PropertyValue></Category><Category name=\"Text Editor_General\" Category=\"{c178af61-531a-46f0-bd57-102d9e42c711}\" Package=\"{e269b994-ef71-4ce0-8bcd-581c217372e8}\" RegisteredName=\"Text Editor_General\" PackageName=\"Microsoft.VisualStudio.Editor.Implementation.EditorPackage\"><PropertyValue name=\"EditorEmulation\">0</PropertyValue><PropertyValue name=\"DragNDropTextEditing\">false</PropertyValue><PropertyValue name=\"SelectSubwordOnDoubleClick\">false</PropertyValue><PropertyValue name=\"CutOrCopyIfNoSelection\">false</PropertyValue><PropertyValue name=\"AutoDelimiterHighlighting\">true</PropertyValue><PropertyValue name=\"TrackChanges\">true</PropertyValue><PropertyValue name=\"DetectUTF8WithoutSignature\">true</PropertyValue><PropertyValue name=\"EnableCodingConventions\">true</PropertyValue><PropertyValue name=\"EnableAudioCues\">false</PropertyValue><PropertyValue name=\"EnableClickableGotoDefinition\">false</PropertyValue><PropertyValue name=\"ClickGoToDefModifierKey\">2</PropertyValue><PropertyValue name=\"OpenDefinitionInPeekView\">false</PropertyValue><PropertyValue name=\"EnableStickyScroll\">false</PropertyValue><PropertyValue name=\"MaxLineOfStickyScroll\">5</PropertyValue><PropertyValue name=\"StickyScrollScopeOverflowKind\">0</PropertyValue><PropertyValue name=\"VisibleWhitespace\">true</PropertyValue><PropertyValue name=\"ShowBidirectionalTextControlCharacters\">true</PropertyValue><PropertyValue name=\"ShowZeroWidthCharacters\">false</PropertyValue><PropertyValue name=\"EnableBracePairColorization\">false</PropertyValue><PropertyValue name=\"SelectionMargin\">true</PropertyValue><PropertyValue name=\"MarginIndicatorBar\">true</PropertyValue><PropertyValue name=\"HighlightCurrentLine\">true</PropertyValue><PropertyValue name=\"ShowBlockStructure\">true</PropertyValue><PropertyValue name=\"ShowErrorSuiggles\">true</PropertyValue><PropertyValue name=\"ShowSelectionMatches\">true</PropertyValue><PropertyValue name=\"ShowFileHealthIndicator\">true</PropertyValue><PropertyValue name=\"LineSpacing\">1</PropertyValue><PropertyValue name=\"ShowEditingStateMargins\">true</PropertyValue><PropertyValue name=\"ShowRowColMargins\">true</PropertyValue><PropertyValue name=\"ShowSelectionStateMargin\">true</PropertyValue><PropertyValue name=\"ShowInsertMargin\">true</PropertyValue><PropertyValue name=\"ShowLeadingWhitespaceMargin\">true</PropertyValue><PropertyValue name=\"ShowLineEndingMargin\">true</PropertyValue></Category><Category name=\"WebForms Tag Specific\" Category=\"{7583bb73-a453-40e8-8951-b6986ddb1d9b}\" Package=\"{1B437D20-F8FE-11D2-A6AE-00104BCC7269}\" RegisteredName=\"WebForms Tag Specific\" PackageName=\"Visual Studio HTM Editor Package\"/><Category name=\"WebProjects\" Category=\"{C78AFEEB-A722-4cf3-AD3F-8694126C2145}\" Package=\"{39c9c826-8ef8-4079-8c95-428f5b1c323f}\" RegisteredName=\"WebProjects\" PackageName=\"Visual Web Developer Project System Package\">\n\t\t<PropertyValue name=\"AutoShowDataConnections\">true</PropertyValue>\n\t\t<PropertyValue name=\"DisableLocalApplicationInsights\">false</PropertyValue>\n\t\t<PropertyValue name=\"EnableDebugTargetsObserver\">true</PropertyValue>\n\t\t<PropertyValue name=\"Use64BitIISExpress\">false</PropertyValue>\n\t\t<PropertyValue name=\"WarnBeforeRunIfErrors\">false</PropertyValue>\n\t</Category><Category name=\"WindowsFormsDesigner_General\" Category=\"{de03177c-918d-40af-8c17-c9d63670c175}\" Package=\"{7b5d447b-0b12-41ea-a84e-c822034422d4}\" RegisteredName=\"WindowsFormsDesigner_General\" PackageName=\"Windows Forms Designer Resources Package\">\n\t\t<PropertyValue name=\"AutoToolboxPopulate\">True</PropertyValue>\n\t\t<PropertyValue name=\"CapitalizeEventHandlerMethodNames\">False</PropertyValue>\n\t\t<PropertyValue name=\"DesignToolsServerConnectionTimeout\">120</PropertyValue>\n\t\t<PropertyValue name=\"EnableInSituEditing\">True</PropertyValue>\n\t\t<PropertyValue name=\"EnableRefactoringOnRename\">True</PropertyValue>\n\t\t<PropertyValue name=\"GridSize\">8, 8</PropertyValue>\n\t\t<PropertyValue name=\"LayoutMode\">SnapLines</PropertyValue>\n\t\t<PropertyValue name=\"LoggingLevel\">None</PropertyValue>\n\t\t<PropertyValue name=\"LoggingLevel2\">Warnings</PropertyValue>\n\t\t<PropertyValue name=\"ObjectBoundSmartTagAutoShow\">True</PropertyValue>\n\t\t<PropertyValue name=\"ShowGrid\">True</PropertyValue>\n\t\t<PropertyValue name=\"ShowInfoBarForHighDpiDesignerMode\">True</PropertyValue>\n\t\t<PropertyValue name=\"SnapToGrid\">True</PropertyValue>\n\t\t<PropertyValue name=\"UseOptimizedCodeGeneration\">True</PropertyValue>\n\t</Category><Category name=\"Work Items_TeamFoundation\" Category=\"{89E620E1-9593-402C-905A-B1944CED0C38}\" Package=\"{CA39E596-31ED-4B34-AA36-5F0240457A7E}\" RegisteredName=\"Work Items_TeamFoundation\" PackageName=\"Microsoft.VisualStudio.TeamFoundation.WorkItemTracking.WitPackage, Microsoft.VisualStudio.TeamFoundation.WorkItemTracking\"/><Category name=\"XmlEditor\" Category=\"{4E1CDE49-B840-4826-8CC6-8FB80D7EF455}\" Package=\"{87569308-4813-40a0-9cd0-d7a30838ca3f}\" RegisteredName=\"XmlEditor\" PackageName=\"Visual Studio XML Editor Package\">\n\t\t<PropertyValue name=\"AttributeFormatting\">None</PropertyValue>\n\t\t<PropertyValue name=\"AutoDownload\">False</PropertyValue>\n\t\t<PropertyValue name=\"AutoInsertAttributeQuotes\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoInsertEndTags\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoInsertNamespaceDeclarations\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoInsertOther\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoOutlining\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoReformatOnEndTag\">True</PropertyValue>\n\t\t<PropertyValue name=\"AutoReformatOnPaste\">True</PropertyValue>\n\t\t<PropertyValue name=\"FormatMixedContentByDefault\">True</PropertyValue>\n\t\t<PropertyValue name=\"SchemaCacheLocation\">%VsInstallDir%\\xml\\Schemas</PropertyValue>\n\t</Category><Category name=\"Yaml_General\" Category=\"{83c839a2-64b3-44bc-bb7a-4ced7f9fcb55}\" Package=\"{9b434dbd-8747-408e-aae0-6efc1f43c75d}\" RegisteredName=\"Yaml_General\" PackageName=\"YamlPackage\"/><Category name=\"Rest_Advanced\" Category=\"{0c011b09-cbde-377f-9cc3-325d1ec31b73}\" Package=\"{70a33ee9-3617-487c-a918-66212bc6b2ec}\" RegisteredName=\"Rest_Advanced\" PackageName=\"RestPackage\"/></UserSettings>"
  },
  {
    "path": "msvc2022/prebuild.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>16.0</VCProjectVersion>\n    <Keyword>Win32Proj</Keyword>\n    <ProjectGuid>{9f53c795-2a93-4154-8b04-bb1829d67602}</ProjectGuid>\n    <RootNamespace>prebuild</RootNamespace>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Utility</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Utility</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Utility</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Utility</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" />\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <SDLCheck>true</SDLCheck>\n      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <ConformanceMode>true</ConformanceMode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <SDLCheck>true</SDLCheck>\n      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <ConformanceMode>true</ConformanceMode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <ConformanceMode>true</ConformanceMode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <ConformanceMode>true</ConformanceMode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <CustomBuild Include=\"..\\scripts\\prebuild.bat\">\n      <FileType>Document</FileType>\n      <Command Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">$(SolutionDir)..\\scripts\\prebuild.bat $(Platform)</Command>\n      <Command Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">$(SolutionDir)..\\scripts\\prebuild.bat $(Platform)</Command>\n      <Command Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">$(SolutionDir)..\\scripts\\prebuild.bat $(Platform)</Command>\n      <Command Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">$(SolutionDir)..\\scripts\\prebuild.bat $(Platform)</Command>\n      <Outputs Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">$(SolutionDir)..\\inc\\rt\\version.h</Outputs>\n      <Outputs Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">$(SolutionDir)..\\inc\\rt\\version.h</Outputs>\n      <Outputs Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">$(SolutionDir)..\\inc\\rt\\version.h</Outputs>\n      <Outputs Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">$(SolutionDir)..\\inc\\rt\\version.h</Outputs>\n      <LinkObjects Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">false</LinkObjects>\n      <LinkObjects Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">false</LinkObjects>\n      <LinkObjects Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">false</LinkObjects>\n      <LinkObjects Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">false</LinkObjects>\n      <Message Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">prebuild</Message>\n      <Message Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">prebuild</Message>\n      <Message Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">prebuild</Message>\n      <Message Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">prebuild</Message>\n    </CustomBuild>\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"version.vcxproj\">\n      <Project>{0ea9bf0c-402b-4852-bd16-644244f0d1b8}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/prebuild.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <CustomBuild Include=\"..\\scripts\\prebuild.bat\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/rt.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\rt\\rt_args.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_atomics.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_backtrace.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_clipboard.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_clock.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_config.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_core.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_debug.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_files.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_generics.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_glyphs.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_heap.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_loader.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_mem.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_nls.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_num.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_processes.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_static.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_std.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_str.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_streams.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_threads.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_vigil.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_win32.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\rt_work.h\" />\n    <ClInclude Include=\"..\\inc\\rt\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\rt\\version.rc.in\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\rt\\rt_args.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_atomics.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_backtrace.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_clipboard.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_clock.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_config.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_core.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_debug.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_files.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_generics.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_heap.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_loader.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_mem.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_nls.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_num.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_processes.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_static.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_str.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_streams.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_threads.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_vigil.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_win32.c\" />\n    <ClCompile Include=\"..\\src\\rt\\rt_work.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"amalgamate.vcxproj\">\n      <Project>{1ea9bf0c-402b-4852-bd16-644244f0d1b9}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>17.0</VCProjectVersion>\n    <Keyword>Win32Proj</Keyword>\n    <ProjectGuid>{8b9ac256-a764-474a-ad7a-31411fe694e1}</ProjectGuid>\n    <RootNamespace>rt</RootNamespace>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" />\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <Optimization>Disabled</Optimization>\n      <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/rt.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"inc\">\n      <UniqueIdentifier>{4c769adc-9483-4a7d-86f9-288a6f0c0b7e}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"inc\\rt\">\n      <UniqueIdentifier>{024f6238-556d-4e5c-80a5-c4e0c49f8ce2}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"src\">\n      <UniqueIdentifier>{a4450c50-a72b-444f-be12-c4ebf4fe6b4a}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"src\\rt\">\n      <UniqueIdentifier>{c6383183-3d91-4a7b-9c26-cb1405bf1216}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\rt\\rt_args.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_atomics.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_clipboard.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_clock.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_config.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_debug.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_files.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_heap.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_loader.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_mem.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_num.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_processes.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_static.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_std.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_streams.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_threads.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\version.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_vigil.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_win32.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_generics.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_nls.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_glyphs.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_str.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_work.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_core.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\rt\\rt_backtrace.h\">\n      <Filter>inc\\rt</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\rt\\version.rc.in\">\n      <Filter>inc\\rt</Filter>\n    </None>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\rt\\rt_args.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_atomics.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_clipboard.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_clock.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_config.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_debug.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_files.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_heap.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_loader.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_mem.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_num.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_processes.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_static.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_streams.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_threads.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_vigil.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_generics.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_nls.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_str.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_work.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_win32.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_core.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\rt\\rt_backtrace.c\">\n      <Filter>src\\rt</Filter>\n    </ClCompile>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample1.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample</RootNamespace>\n    <ProjectName>sample1</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample1.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <UseLibraryDependencyInputs>true</UseLibraryDependencyInputs>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample1.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample1.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample2.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample2</RootNamespace>\n    <ProjectName>sample2</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample2.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample2.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{933dd3c6-58dd-453e-9d20-0c2269f78756}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{111da25f-48b8-4da1-91c2-4b99ca8f45bb}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{37839352-8a1d-4f6f-90d1-cac9f87295ae}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample2.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample3.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample3</RootNamespace>\n    <ProjectName>sample3</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample3.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <Private>true</Private>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample3.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{a68dae01-11a7-4240-9c5e-fe8e52b54a09}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{41933a71-6dc4-43f3-bdc6-a6455b161d33}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{71fad776-115b-499a-ac1d-020e1111e537}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample3.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample4.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample</RootNamespace>\n    <ProjectName>sample4</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n    <ClInclude Include=\"..\\stb_image.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n    <Image Include=\"..\\src\\samples\\sample.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample4.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample4.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\stb_image.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{6af6d069-4c05-45fb-aa18-19f2b315b0f2}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{7891cc09-ee32-488d-a56a-9d5497eb8eb4}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{ab08c9f3-f30a-47cc-a6ac-e8111b064190}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n    <Image Include=\"..\\src\\samples\\sample.png\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample4.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample5.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample</RootNamespace>\n    <ProjectName>sample5</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\edit.test.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample5.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample5.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{611fddac-73f7-4eca-bb48-4cdd4434cd95}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{8afe0792-28fa-4e94-8862-1af8b9675554}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{0ea3b32f-55db-4ed9-82d8-a178df0955c9}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample5.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\edit.test.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample6.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{8930DB4B-FF85-4434-A9FD-4251F6B0749C}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample</RootNamespace>\n    <ProjectName>sample6</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\stb_image.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\gotg.gif\" />\n    <Image Include=\"..\\src\\samples\\groot.gif\" />\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n    <Image Include=\"..\\src\\samples\\sample.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample6.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample6.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\src\\samples\\mr_blue_sky.midi\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample6.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\stb_image.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{85eb12e3-d695-4bdf-bdf0-18e9b0664a88}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{8e38243b-66e1-443c-8007-2e77d12bbf58}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{88bb860e-71d0-4b1b-a521-c1c35ba5ce20}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n    <Image Include=\"..\\src\\samples\\sample.png\">\n      <Filter>res</Filter>\n    </Image>\n    <Image Include=\"..\\src\\samples\\groot.gif\">\n      <Filter>res</Filter>\n    </Image>\n    <Image Include=\"..\\src\\samples\\gotg.gif\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample6.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample6.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\stb_image.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\src\\samples\\mr_blue_sky.midi\">\n      <Filter>res</Filter>\n    </None>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample7.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample7</RootNamespace>\n    <ProjectName>sample7</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample7.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample7.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{0891e55a-05d8-46ba-b881-db39fe379194}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{78dd48ce-ee1d-4d53-a958-b06af38243cc}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{5b6cb884-4d07-44cd-b604-88b712143ff8}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample7.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample8.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample</RootNamespace>\n    <ProjectName>sample8</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample8.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <UseLibraryDependencyInputs>true</UseLibraryDependencyInputs>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample8.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\inc\\ut\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample8.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Xml Include=\"manifest.xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\inc\\ut\\version.rc.in\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample9.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>sample9</RootNamespace>\n    <ProjectName>sample9</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>MultiByte</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets>\n    </CustomBuildAfterTargets>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <CustomBuildAfterTargets />\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <CodeAnalysisRuleSet>NativeMinimumRules.ruleset</CodeAnalysisRuleSet>\n    <GenerateManifest>false</GenerateManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\src\\samples;$(ProjectDir)..\\single_file_lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\" />\n    <ClInclude Include=\"..\\src\\samples\\version.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\sample9.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"ui.vcxproj\">\n      <Project>{9b9ac256-a764-474a-ad7a-31411fe694e2}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/sample9.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClInclude Include=\"..\\src\\samples\\i18n.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\src\\samples\\version.h\">\n      <Filter>res</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\single_file_lib\\ui\\ui.h\">\n      <Filter>single_file_lib\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"res\">\n      <UniqueIdentifier>{c60b96ea-15fe-4796-86c9-3b9428dc82c7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{0b936939-cf81-415a-861e-4c0d87625788}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{96a3d1bd-6d12-44d3-9226-8ee7bd0e87f8}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\ui\">\n      <UniqueIdentifier>{4d363118-820b-458d-b1c4-9b3cdfa6a3be}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\src\\samples\\sample.ico\">\n      <Filter>res</Filter>\n    </Image>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\src\\samples\\sample.rc\">\n      <Filter>res</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\samples\\sample9.c\" />\n    <ClCompile Include=\"..\\src\\samples\\ui.c\" />\n    <ClCompile Include=\"..\\src\\samples\\rt.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/test1.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{3EA9BF0C-402B-4852-BD16-644255F0D1B7}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>test1</RootNamespace>\n    <ProjectName>test1</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies />\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n      <IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <Optimization>Disabled</Optimization>\n      <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\test\\test1.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\test\\test1.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\test\\version.rc.in\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n</Project>"
  },
  {
    "path": "msvc2022/test1.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\test\\test1.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"rc\">\n      <UniqueIdentifier>{be5e3ac4-0df0-4828-adc5-d9e917138711}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"..\\test\\test1.rc\">\n      <Filter>rc</Filter>\n    </ResourceCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\test\\version.rc.in\">\n      <Filter>rc</Filter>\n    </None>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/test2.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{4EA9BF0C-402B-4852-BD61-644255F0D1B8}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>test2</RootNamespace>\n    <ProjectName>test2</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <IgnoreImportLibrary>true</IgnoreImportLibrary>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies />\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalOptions>/NOIMPLIB %(AdditionalOptions)</AdditionalOptions>\n    </Link>\n    <ClCompile />\n    <ClCompile>\n      <PreprocessorDefinitions>RT_TESTS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n    </ClCompile>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\test\\test2.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"amalgamate.vcxproj\">\n      <Project>{1ea9bf0c-402b-4852-bd16-644244f0d1b9}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n    <ProjectReference Include=\"prebuild.vcxproj\">\n      <Project>{9f53c795-2a93-4154-8b04-bb1829d67602}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n      <LinkLibraryDependencies>false</LinkLibraryDependencies>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n</Project>"
  },
  {
    "path": "msvc2022/test2.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\test\\test2.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"single_file_lib\">\n      <UniqueIdentifier>{965f87a5-7aa4-48a6-8f61-70479fd74ef6}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"single_file_lib\\rt\">\n      <UniqueIdentifier>{5fb84469-51a5-4305-9c2e-20874e45dff6}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\single_file_lib\\rt\\rt.h\">\n      <Filter>single_file_lib\\rt</Filter>\n    </ClInclude>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/ui.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.3.32804.467\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \".misc\", \".misc\", \"{FA83895A-1CED-4FC5-9F11-6AFA109CFC7D}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t.editorconfig = .editorconfig\n\t\t..\\.gitignore = ..\\.gitignore\n\t\t..\\.github\\workflows\\build-on-push.yml = ..\\.github\\workflows\\build-on-push.yml\n\t\tcommon.props = common.props\n\t\texclusion.dic = exclusion.dic\n\t\t..\\README.md = ..\\README.md\n\tEndProjectSection\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"test1\", \"test1.vcxproj\", \"{3EA9BF0C-402B-4852-BD16-644255F0D1B7}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"version\", \"version.vcxproj\", \"{0EA9BF0C-402B-4852-BD16-644244F0D1B8}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"amalgamate\", \"amalgamate.vcxproj\", \"{1EA9BF0C-402B-4852-BD16-644244F0D1B9}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"prebuild\", \"prebuild.vcxproj\", \"{9F53C795-2A93-4154-8B04-BB1829D67602}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"test2\", \"test2.vcxproj\", \"{4EA9BF0C-402B-4852-BD61-644255F0D1B8}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample1\", \"sample1.vcxproj\", \"{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"ui\", \"ui.vcxproj\", \"{9B9AC256-A764-474A-AD7A-31411FE694E2}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"rt\", \"rt.vcxproj\", \"{8B9AC256-A764-474A-AD7A-31411FE694E1}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample2\", \"sample2.vcxproj\", \"{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample3\", \"sample3.vcxproj\", \"{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample4\", \"sample4.vcxproj\", \"{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample5\", \"sample5.vcxproj\", \"{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample6\", \"sample6.vcxproj\", \"{8930DB4B-FF85-4434-A9FD-4251F6B0749C}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample9\", \"sample9.vcxproj\", \"{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample7\", \"sample7.vcxproj\", \"{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"sample8\", \"sample8.vcxproj\", \"{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"samples\", \"samples\", \"{9CACB4C0-49E4-4B37-B45A-B36F2CAA7A15}\"\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"groot\", \"groot.vcxproj\", \"{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tdebug|arm64 = debug|arm64\n\t\tdebug|x64 = debug|x64\n\t\trelease|arm64 = release|arm64\n\t\trelease|x64 = release|x64\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.debug|arm64.Build.0 = debug|arm64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.debug|x64.ActiveCfg = debug|x64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.debug|x64.Build.0 = debug|x64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.release|arm64.ActiveCfg = release|arm64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.release|arm64.Build.0 = release|arm64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.release|x64.ActiveCfg = release|x64\n\t\t{3EA9BF0C-402B-4852-BD16-644255F0D1B7}.release|x64.Build.0 = release|x64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.debug|arm64.Build.0 = debug|arm64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.debug|x64.ActiveCfg = debug|x64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.debug|x64.Build.0 = debug|x64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.release|arm64.ActiveCfg = release|arm64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.release|arm64.Build.0 = release|arm64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.release|x64.ActiveCfg = release|x64\n\t\t{0EA9BF0C-402B-4852-BD16-644244F0D1B8}.release|x64.Build.0 = release|x64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.debug|arm64.Build.0 = debug|arm64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.debug|x64.ActiveCfg = debug|x64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.debug|x64.Build.0 = debug|x64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.release|arm64.ActiveCfg = release|arm64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.release|arm64.Build.0 = release|arm64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.release|x64.ActiveCfg = release|x64\n\t\t{1EA9BF0C-402B-4852-BD16-644244F0D1B9}.release|x64.Build.0 = release|x64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.debug|arm64.Build.0 = debug|arm64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.debug|x64.ActiveCfg = debug|x64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.debug|x64.Build.0 = debug|x64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.release|arm64.ActiveCfg = release|arm64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.release|arm64.Build.0 = release|arm64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.release|x64.ActiveCfg = release|x64\n\t\t{9F53C795-2A93-4154-8B04-BB1829D67602}.release|x64.Build.0 = release|x64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.debug|arm64.Build.0 = debug|arm64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.debug|x64.ActiveCfg = debug|x64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.debug|x64.Build.0 = debug|x64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.release|arm64.ActiveCfg = release|arm64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.release|arm64.Build.0 = release|arm64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.release|x64.ActiveCfg = release|x64\n\t\t{4EA9BF0C-402B-4852-BD61-644255F0D1B8}.release|x64.Build.0 = release|x64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.debug|arm64.ActiveCfg = Debug|arm64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.debug|arm64.Build.0 = Debug|arm64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.debug|x64.ActiveCfg = Debug|x64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.debug|x64.Build.0 = Debug|x64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.release|arm64.ActiveCfg = Release|arm64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.release|arm64.Build.0 = Release|arm64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.release|x64.ActiveCfg = Release|x64\n\t\t{4A21BE1F-678C-4733-A9F0-A7BFFFCF3CC2}.release|x64.Build.0 = Release|x64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.debug|arm64.Build.0 = debug|arm64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.debug|x64.ActiveCfg = debug|x64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.debug|x64.Build.0 = debug|x64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.release|arm64.ActiveCfg = release|arm64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.release|arm64.Build.0 = release|arm64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.release|x64.ActiveCfg = release|x64\n\t\t{9B9AC256-A764-474A-AD7A-31411FE694E2}.release|x64.Build.0 = release|x64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.debug|arm64.Build.0 = debug|arm64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.debug|x64.ActiveCfg = debug|x64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.debug|x64.Build.0 = debug|x64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.release|arm64.ActiveCfg = release|arm64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.release|arm64.Build.0 = release|arm64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.release|x64.ActiveCfg = release|x64\n\t\t{8B9AC256-A764-474A-AD7A-31411FE694E1}.release|x64.Build.0 = release|x64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.debug|arm64.Build.0 = debug|arm64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.debug|x64.ActiveCfg = debug|x64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.debug|x64.Build.0 = debug|x64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.release|arm64.ActiveCfg = release|arm64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.release|arm64.Build.0 = release|arm64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.release|x64.ActiveCfg = release|x64\n\t\t{0B45C2EE-3A7E-46B0-8B8D-5DB62BF75A43}.release|x64.Build.0 = release|x64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.debug|arm64.Build.0 = debug|arm64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.debug|x64.ActiveCfg = debug|x64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.debug|x64.Build.0 = debug|x64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.release|arm64.ActiveCfg = release|arm64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.release|arm64.Build.0 = release|arm64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.release|x64.ActiveCfg = release|x64\n\t\t{0B45C2EE-3A7E-46B0-3B3D-5DB62BF75A43}.release|x64.Build.0 = release|x64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.debug|arm64.Build.0 = debug|arm64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.debug|x64.ActiveCfg = debug|x64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.debug|x64.Build.0 = debug|x64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.release|arm64.ActiveCfg = release|arm64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.release|arm64.Build.0 = release|arm64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.release|x64.ActiveCfg = release|x64\n\t\t{4A12BE1F-678C-4733-A9F0-A7BFFFCF3CC4}.release|x64.Build.0 = release|x64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.debug|arm64.Build.0 = debug|arm64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.debug|x64.ActiveCfg = debug|x64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.debug|x64.Build.0 = debug|x64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.release|arm64.ActiveCfg = release|arm64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.release|arm64.Build.0 = release|arm64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.release|x64.ActiveCfg = release|x64\n\t\t{4A21BE1F-678C-4733-A9F1-A8BFFFCF3CC2}.release|x64.Build.0 = release|x64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.debug|arm64.Build.0 = debug|arm64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.debug|x64.ActiveCfg = debug|x64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.debug|x64.Build.0 = debug|x64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.release|arm64.ActiveCfg = release|arm64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.release|arm64.Build.0 = release|arm64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.release|x64.ActiveCfg = release|x64\n\t\t{8930DB4B-FF85-4434-A9FD-4251F6B0749C}.release|x64.Build.0 = release|x64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.debug|arm64.Build.0 = debug|arm64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.debug|x64.ActiveCfg = debug|x64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.debug|x64.Build.0 = debug|x64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.release|arm64.ActiveCfg = release|arm64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.release|arm64.Build.0 = release|arm64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.release|x64.ActiveCfg = release|x64\n\t\t{1B99C2EE-3A7E-46B0-3B3D-5DB99BF75A34}.release|x64.Build.0 = release|x64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.debug|arm64.Build.0 = debug|arm64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.debug|x64.ActiveCfg = debug|x64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.debug|x64.Build.0 = debug|x64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.release|arm64.ActiveCfg = release|arm64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.release|arm64.Build.0 = release|arm64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.release|x64.ActiveCfg = release|x64\n\t\t{1C3416BF-18B1-5CB1-8D33-A3DACA40AB5D}.release|x64.Build.0 = release|x64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.debug|arm64.ActiveCfg = Debug|arm64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.debug|arm64.Build.0 = Debug|arm64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.debug|x64.ActiveCfg = Debug|x64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.debug|x64.Build.0 = Debug|x64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.release|arm64.ActiveCfg = Release|arm64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.release|arm64.Build.0 = Release|arm64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.release|x64.ActiveCfg = Release|x64\n\t\t{7A21BE1F-678C-4733-A9F0-A7BEEECF3CCE}.release|x64.Build.0 = Release|x64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.debug|arm64.ActiveCfg = debug|arm64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.debug|arm64.Build.0 = debug|arm64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.debug|x64.ActiveCfg = debug|x64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.debug|x64.Build.0 = debug|x64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.release|arm64.ActiveCfg = release|arm64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.release|arm64.Build.0 = release|arm64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.release|x64.ActiveCfg = release|x64\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3}.release|x64.Build.0 = release|x64\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{4A21BE1F-678C-4733-ABF0-A7BFFFCF2CC3} = {9CACB4C0-49E4-4B37-B45A-B36F2CAA7A15}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {4903FF9C-ADBE-4753-BB20-C4EEE5D83493}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "msvc2022/ui.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\ui\\ui_caption.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_containers.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_app.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_button.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_edit_doc.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_edit_view.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_fuzzing.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_image.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_midi.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_theme.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_toggle.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_colors.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_core.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_gdi.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_label.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_mbx.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_slider.h\" />\n    <ClInclude Include=\"..\\inc\\ui\\ui_view.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\ui\\ui_app.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_button.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_caption.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_containers.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_edit_doc.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_edit_view.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_fuzzing.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_image.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_midi.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_theme.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_toggle.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_colors.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_core.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_gdi.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_label.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_mbx.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_slider.c\" />\n    <ClCompile Include=\"..\\src\\ui\\ui_view.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"amalgamate.vcxproj\">\n      <Project>{1ea9bf0c-402b-4852-bd16-644244f0d1b9}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"rt.vcxproj\">\n      <Project>{8b9ac256-a764-474a-ad7a-31411fe694e1}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>17.0</VCProjectVersion>\n    <Keyword>Win32Proj</Keyword>\n    <ProjectGuid>{9b9ac256-a764-474a-ad7a-31411fe694e2}</ProjectGuid>\n    <RootNamespace>ui</RootNamespace>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" />\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <ClCompile />\n    <Link>\n      <SubSystem>\n      </SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n    </Link>\n    <PostBuildEvent>\n      <Command>pushd $(ProjectDir).. &amp;&amp; $(ProjectDir)..\\bin\\$(Configuration)\\$(Platform)\\amalgamate.exe $(ProjectName) &gt; single_file_lib\\$(ProjectName)\\$(ProjectName).h &amp;&amp; popd</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>amalgamate \"$(ProjectName)\" into single_file_lib\\$(ProjectName)\\$(ProjectName).h</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "msvc2022/ui.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"inc\">\n      <UniqueIdentifier>{4c769adc-9483-4a7d-86f9-288a6f0c0b9e}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"inc\\ui\">\n      <UniqueIdentifier>{24026435-1a60-4f78-a1fd-e3c4f12e5885}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"src\">\n      <UniqueIdentifier>{a4450c50-a72b-444f-be12-c4ebf4fe5b4a}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"src\\ui\">\n      <UniqueIdentifier>{72fd0a31-5562-44eb-b991-4130091643f4}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\inc\\ui\\ui_app.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_button.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_toggle.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_colors.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_core.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_gdi.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_label.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_mbx.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_slider.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_view.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_caption.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_containers.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_theme.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_edit_view.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_edit_doc.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_fuzzing.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_image.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\inc\\ui\\ui_midi.h\">\n      <Filter>inc\\ui</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\ui\\ui_app.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_button.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_toggle.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_colors.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_core.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_gdi.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_label.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_mbx.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_slider.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_view.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_caption.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_containers.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_theme.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_edit_view.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_edit_doc.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_fuzzing.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_image.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\src\\ui\\ui_midi.c\">\n      <Filter>src\\ui</Filter>\n    </ClCompile>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "msvc2022/version.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"debug|arm64\">\n      <Configuration>debug</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"debug|x64\">\n      <Configuration>debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|arm64\">\n      <Configuration>release</Configuration>\n      <Platform>arm64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"release|x64\">\n      <Configuration>release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\tools\\version.c\" />\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{0EA9BF0C-402B-4852-BD16-644244F0D1B8}</ProjectGuid>\n    <Keyword>Win32Proj</Keyword>\n    <RootNamespace>version</RootNamespace>\n    <ProjectName>version</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"Configuration\">\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n    <Import Project=\"common.props\" />\n  </ImportGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='debug|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|x64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='release|arm64'\">\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n</Project>"
  },
  {
    "path": "msvc2022/version.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\src\\tools\\version.c\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "scripts/clean.bat",
    "content": "@echo off\nrmdir /s /q ..\\bin\nrmdir /s /q ..\\build\nrmdir /s /q ..\\lib\nrmdir /s /q ..\\msvc2022\\.vs\ndel /q ..\\inc\\ut\\version.h\n\n"
  },
  {
    "path": "scripts/prebuild.bat",
    "content": "@echo off\npushd ..\nif exist bin\\release\\%1\\version.exe (\n    bin\\release\\%1\\version.exe > inc/rt/version.h 2>nul\n    goto version_done\n)\nif exist bin\\debug\\%1\\version.exe (\n    bin\\debug\\%1\\version.exe > inc/rt/version.h 2>nul\n    goto version_done\n)\n:version_done\npopd\nexit /b 0"
  },
  {
    "path": "single_file_lib/rt/rt.h",
    "content": "#ifndef rt_definition\n#define rt_definition\n\n// _________________________________ rt_std.h _________________________________\n\n#include <ctype.h>\n#include <errno.h>\n#include <float.h>\n#include <limits.h>\n#include <locale.h>\n#include <malloc.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define rt_stringify(x) #x\n#define rt_tostring(x) rt_stringify(x)\n#define rt_pragma(x) _Pragma(rt_tostring(x))\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#pragma GCC diagnostic ignored \"-Wfour-char-constants\"\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#pragma GCC diagnostic ignored \"-Wmissing-noreturn\"\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"\n#pragma GCC diagnostic ignored \"-Wcast-align\"\n#pragma GCC diagnostic ignored \"-Waddress-of-packed-member\"\n#pragma GCC diagnostic ignored \"-Wused-but-marked-unused\" // because in debug only\n#define rt_msvc_pragma(x)\n#define rt_gcc_pragma(x) rt_pragma(x)\n#else\n#define rt_gcc_pragma(x)\n#define rt_msvc_pragma(x) rt_pragma(x)\n#endif\n\n#ifdef _MSC_VER\n    #define rt_suppress_constant_cond_exp _Pragma(\"warning(suppress: 4127)\")\n#else\n    #define rt_suppress_constant_cond_exp\n#endif\n\n// Type aliases for floating-point types similar to <stdint.h>\ntypedef float  fp32_t;\ntypedef double fp64_t;\n// \"long fp64_t\" is required by C standard but the bitness\n// of it is not specified.\n\n#ifdef __cplusplus\n    #define rt_begin_c extern \"C\" {\n    #define rt_end_c } // extern \"C\"\n#else\n    #define rt_begin_c // C headers compiled as C++\n    #define rt_end_c\n#endif\n\n// rt_countof() and rt_countof() are suitable for\n// small < 2^31 element arrays\n\n#define rt_countof(a) ((int32_t)((int)(sizeof(a) / sizeof((a)[0]))))\n\n#if defined(__GNUC__) || defined(__clang__)\n    #define rt_force_inline __attribute__((always_inline))\n#elif defined(_MSC_VER)\n    #define rt_force_inline __forceinline\n#endif\n\n#ifndef __cplusplus\n    #define null ((void*)0) // better than NULL which is zero\n#else\n    #define null nullptr\n#endif\n\n#if defined(_MSC_VER)\n    #define rt_thread_local __declspec(thread)\n#else\n    #ifndef __cplusplus\n        #define rt_thread_local _Thread_local // C99\n    #else\n        // C++ supports rt_thread_local keyword\n    #endif\n#endif\n\n// rt_begin_packed rt_end_packed\n// usage: typedef rt_begin_packed struct foo_s { ... } rt_end_packed foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_attribute_packed __attribute__((packed))\n#define rt_begin_packed\n#define rt_end_packed rt_attribute_packed\n#else\n#define rt_begin_packed rt_pragma( pack(push, 1) )\n#define rt_end_packed rt_pragma( pack(pop) )\n#define rt_attribute_packed\n#endif\n\n// usage: typedef struct rt_aligned_8 foo_s { ... } foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_aligned_8 __attribute__((aligned(8)))\n#elif defined(_MSC_VER)\n#define rt_aligned_8 __declspec(align(8))\n#else\n#define rt_aligned_8\n#endif\n\n\n// In callbacks the formal parameters are\n// frequently unused. Also sometimes parameters\n// are used in debug configuration only (e.g. rt_assert() checks)\n// but not in release.\n// C does not have anonymous parameters like C++\n// Instead of:\n//      void foo(param_type_t param) { (void)param; / *unused */ }\n// use:\n//      vod foo(param_type_t rt_unused(param)) { }\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_unused(name) name __attribute__((unused))\n#elif defined(_MSC_VER)\n#define rt_unused(name) _Pragma(\"warning(suppress:  4100)\") name\n#else\n#define rt_unused(name) name\n#endif\n\n// Because MS C compiler is unhappy about alloca() and\n// does not implement (C99 optional) dynamic arrays on the stack:\n\n#define rt_stackalloc(n) (_Pragma(\"warning(suppress: 6255 6263)\") alloca(n))\n\n// alloca() is messy and in general is a not a good idea.\n// try to avoid if possible. Stack sizes vary from 64KB to 8MB in 2024.\n\n// _________________________________ rt_str.h _________________________________\n\nrt_begin_c\n\ntypedef struct rt_str64_t {\n    char s[64];\n} rt_str64_t;\n\ntypedef struct rt_str128_t {\n    char s[128];\n} rt_str128_t;\n\ntypedef struct rt_str1024_t {\n    char s[1024];\n} rt_str1024_t;\n\ntypedef struct rt_str32K_t {\n    char s[32 * 1024];\n} rt_str32K_t;\n\n// truncating string printf:\n// char s[100]; rt_str_printf(s, \"Hello %s\", \"world\");\n// do not use with char* and char s[] parameters\n// because rt_countof(s) will be sizeof(char*) not the size of the buffer.\n\n#define rt_str_printf(s, ...) rt_str.format((s), rt_countof(s), \"\" __VA_ARGS__)\n\n#define rt_strerr(r) (rt_str.error((r)).s) // use only as rt_str_printf() parameter\n\n// The strings are expected to be UTF-8 encoded.\n// Copy functions fatal fail if the destination buffer is too small.\n// It is responsibility of the caller to make sure it won't happen.\n\ntypedef struct {\n    char* (*drop_const)(const char* s); // because of strstr() and alike\n    int32_t (*len)(const char* s);\n    int32_t (*len16)(const uint16_t* utf16);\n    int32_t (*utf8bytes)(const char* utf8, int32_t bytes); // 0 on error\n    int32_t (*glyphs)(const char* utf8, int32_t bytes); // -1 on error\n    bool (*starts)(const char* s1, const char* s2);  // s1 starts with s2\n    bool (*ends)(const char* s1, const char* s2);    // s1 ends with s2\n    bool (*istarts)(const char* s1, const char* s2); // ignore case\n    bool (*iends)(const char* s1, const char* s2);   // ignore case\n    // string truncation is fatal use strlen() to check at call site\n    void (*lower)(char* d, int32_t capacity, const char* s); // ASCII only\n    void (*upper)(char* d, int32_t capacity, const char* s); // ASCII only\n    // utf8/utf16 conversion\n    // If `chars` argument is -1, the function utf8_bytes includes the zero\n    // terminating character in the conversion and the returned byte count.\n    int32_t (*utf8_bytes)(const uint16_t* utf16, int32_t bytes); // bytes count\n    // If `bytes` argument is -1, the function utf16_chars() includes the zero\n    // terminating character in the conversion and the returned character count.\n    int32_t (*utf16_chars)(const char* utf8, int32_t bytes); // chars count\n    // utf8_bytes() and utf16_chars() return -1 on invalid UTF-8/UTF-16\n    // utf8_bytes(L\"\", -1) returns 1 for zero termination\n    // utf16_chars(\"\", -1) returns 1 for zero termination\n    // chars: -1 means both source and destination are zero terminated\n    errno_t (*utf16to8)(char* utf8, int32_t capacity,\n                        const uint16_t* utf16, int32_t chars);\n    // bytes: -1 means both source and destination are zero terminated\n    errno_t (*utf8to16)(uint16_t* utf16, int32_t capacity,\n                        const char* utf8, int32_t bytes);\n    // https://compart.com/en/unicode/U+1F41E\n    // Lady Beetle: utf16 L\"\\xD83D\\xDC1E\" utf8 \"\\xF0\\x9F\\x90\\x9E\"\n    //           surrogates:  high   low\n    bool (*utf16_is_low_surrogate)(uint16_t utf16char);\n    bool (*utf16_is_high_surrogate)(uint16_t utf16char);\n    uint32_t (*utf32)(const char* utf8, int32_t bytes); // single codepoint\n    // string formatting printf style:\n    void (*format_va)(char* utf8, int32_t count, const char* format, va_list va);\n    void (*format)(char* utf8, int32_t count, const char* format, ...);\n    // format \"dg\" digit grouped; see below for known grouping separators:\n    const char* (*grouping_separator)(void); // locale\n    // Returned const char* pointer is short-living thread local and\n    // intended to be used in the arguments list of .format() or .printf()\n    // like functions, not stored or passed for prolonged call chains.\n    // See implementation for details.\n    rt_str64_t (*int64_dg)(int64_t v, bool uint, const char* gs);\n    rt_str64_t (*int64)(int64_t v);      // with UTF-8 thin space\n    rt_str64_t (*uint64)(uint64_t v);    // with UTF-8 thin space\n    rt_str64_t (*int64_lc)(int64_t v);   // with locale separator\n    rt_str64_t (*uint64_lc)(uint64_t v); // with locale separator\n    rt_str128_t (*fp)(const char* format, fp64_t v); // respects locale\n    // errors to strings\n    rt_str1024_t (*error)(int32_t error);     // en-US\n    rt_str1024_t (*error_nls)(int32_t error); // national locale string\n    void (*test)(void);\n} rt_str_if;\n\n// Known grouping separators\n// https://en.wikipedia.org/wiki/Decimal_separator#Digit_grouping\n// coma \",\" separated decimal\n// other commonly used separators:\n// underscore \"_\" (Fortran, Kotlin)\n// apostrophe \"'\" (C++14, Rebol, Red)\n// backtick \"`\"\n// space \"\\x20\"\n// thin_space \"\\xE2\\x80\\x89\" Unicode: U+2009\n\nextern rt_str_if rt_str;\n\nrt_end_c\n// ___________________________________ rt.h ___________________________________\n\n// the rest is in alphabetical order (no inter dependencies)\n\n// ________________________________ rt_args.h _________________________________\n\nrt_begin_c\n\ntypedef struct {\n    // On Unix it is responsibility of the main() to assign these values\n    int32_t c;      // argc\n    const char** v; // argv[argc]\n    const char** env; // rt_args.env[] is null-terminated\n    void    (*main)(int32_t argc, const char* argv[], const char** env);\n    void    (*WinMain)(void); // windows specific\n    int32_t (*option_index)(const char* option); // e.g. option: \"--verbosity\" or \"-v\"\n    void    (*remove_at)(int32_t ix);\n    /* argc=3 argv={\"foo\", \"--verbose\"} -> returns true; argc=1 argv={\"foo\"} */\n    bool    (*option_bool)(const char* option);\n    /* argc=3 argv={\"foo\", \"--n\", \"153\"} -> value==153, true; argc=1 argv={\"foo\"}\n       also handles negative values (e.g. \"-153\") and hex (e.g. 0xBADF00D)\n    */\n    bool    (*option_int)(const char* option, int64_t *value);\n    // for argc=3 argv={\"foo\", \"--path\", \"bar\"}\n    //     option_str(\"--path\", option)\n    // returns option: \"bar\" and argc=1 argv={\"foo\"} */\n    const char* (*option_str)(const char* option);\n    // basename() for argc=3 argv={\"/bin/foo.exe\", ...} returns \"foo\":\n    const char* (*basename)(void);\n    void (*fini)(void);\n    void (*test)(void);\n} rt_args_if;\n\nextern rt_args_if rt_args;\n\n/* Usage:\n\n    (both main() and WinMain() could be compiled at the same time on Windows):\n\n    static int run(void);\n\n    int main(int argc, char* argv[], char* envp[]) { // link.exe /SUBSYSTEM:CONSOLE\n        rt_args.main(argc, argv, envp); // Initialize args with command-line parameters\n        int r = run();\n        rt_args.fini();\n        return r;\n    }\n\n    #include \"rt/rt_win32.h\"\n\n    int APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, char* command, int show) {\n        // link.exe /SUBSYSTEM:WINDOWS\n        rt_args.WinMain();\n        int r = run();\n        rt_args.fini();\n        return 0;\n    }\n\n    static int run(void) {\n        if (rt_args.option_bool(\"-v\")) {\n            rt_debug.verbosity.level = rt_debug.verbosity.verbose;\n        }\n        int64_t num = 0;\n        if (rt_args.option_int(\"--number\", &num)) {\n            printf(\"--number: %ld\\n\", num);\n        }\n        const char* path = rt_args.option_str(\"--path\");\n        if (path != null) {\n            printf(\"--path: %s\\n\", path);\n        }\n        printf(\"rt_args.basename(): %s\\n\", rt_args.basename());\n        printf(\"rt_args.v[0]: %s\\n\", rt_args.v[0]);\n        for (int i = 1; i < rt_args.c; i++) {\n            printf(\"rt_args.v[%d]: %s\\n\", i, rt_args.v[i]);\n        }\n        return 0;\n    }\n\n    // Also see: https://github.com/leok7v/ut/blob/main/test/test1.c\n\n*/\n\nrt_end_c\n\n// ______________________________ rt_backtrace.h ______________________________\n\n// \"bt\" stands for Stack Back Trace (not British Telecom)\n\nrt_begin_c\n\nenum { rt_backtrace_max_depth = 32 };    // increase if not enough\nenum { rt_backtrace_max_symbol = 1024 }; // MSFT symbol size limit\n\ntypedef struct thread_s* rt_thread_t;\n\ntypedef char rt_backtrace_symbol_t[rt_backtrace_max_symbol];\ntypedef char rt_backtrace_file_t[512];\n\ntypedef struct rt_backtrace_s {\n    int32_t frames; // 0 if capture() failed\n    uint32_t hash;\n    errno_t  error; // last error set by capture() or symbolize()\n    void* stack[rt_backtrace_max_depth];\n    rt_backtrace_symbol_t symbol[rt_backtrace_max_depth];\n    rt_backtrace_file_t file[rt_backtrace_max_depth];\n    int32_t line[rt_backtrace_max_depth];\n    bool symbolized;\n} rt_backtrace_t;\n\n//  calling .trace(bt, /*stop:*/\"*\")\n//  stops backtrace dumping at any of the known Microsoft runtime\n//  symbols:\n//    \"main\",\n//    \"WinMain\",\n//    \"BaseThreadInitThunk\",\n//    \"RtlUserThreadStart\",\n//    \"mainCRTStartup\",\n//    \"WinMainCRTStartup\",\n//    \"invoke_main\"\n// .trace(bt, null)\n// provides complete backtrace to the bottom of stack\n\ntypedef struct {\n    void (*capture)(rt_backtrace_t *bt, int32_t skip); // number of frames to skip\n    void (*context)(rt_thread_t thread, const void* context, rt_backtrace_t *bt);\n    void (*symbolize)(rt_backtrace_t *bt);\n    // dump backtrace into rt_println():\n    void (*trace)(const rt_backtrace_t* bt, const char* stop);\n    void (*trace_self)(const char* stop);\n    void (*trace_all_but_self)(void); // trace all threads\n    const char* (*string)(const rt_backtrace_t* bt, char* text, int32_t count);\n    void (*test)(void);\n} rt_backtrace_if;\n\nextern rt_backtrace_if rt_backtrace;\n\n#define rt_backtrace_here() do {   \\\n    rt_backtrace_t bt_ = {0};      \\\n    rt_backtrace.capture(&bt_, 0); \\\n    rt_backtrace.symbolize(&bt_);  \\\n    rt_backtrace.trace(&bt_, \"*\"); \\\n} while (0)\n\nrt_end_c\n\n// _______________________________ rt_atomics.h _______________________________\n\n// Will be deprecated soon after Microsoft fully supports <stdatomic.h>\n\nrt_begin_c\n\ntypedef struct {\n    void* (*exchange_ptr)(volatile void** a, void* v); // retuns previous value\n    int32_t (*increment_int32)(volatile int32_t* a); // returns incremented\n    int32_t (*decrement_int32)(volatile int32_t* a); // returns decremented\n    int64_t (*increment_int64)(volatile int64_t* a); // returns incremented\n    int64_t (*decrement_int64)(volatile int64_t* a); // returns decremented\n    int32_t (*add_int32)(volatile int32_t* a, int32_t v); // returns result of add\n    int64_t (*add_int64)(volatile int64_t* a, int64_t v); // returns result of add\n    // returns the value held previously by \"a\" address:\n    int32_t (*exchange_int32)(volatile int32_t* a, int32_t v);\n    int64_t (*exchange_int64)(volatile int64_t* a, int64_t v);\n    // compare_exchange functions compare the *a value with the comparand value.\n    // If the *a is equal to the comparand value, the \"v\" value is stored in the address\n    // specified by \"a\" otherwise, no operation is performed.\n    // returns true if previous value *a was the same as \"comparand\"\n    // false if *a was different from \"comparand\" and \"a\" was NOT modified.\n    bool (*compare_exchange_int64)(volatile int64_t* a, int64_t comparand, int64_t v);\n    bool (*compare_exchange_int32)(volatile int32_t* a, int32_t comparand, int32_t v);\n    bool (*compare_exchange_ptr)(volatile void** a, void* comparand, void* v);\n    void (*spinlock_acquire)(volatile int64_t* spinlock);\n    void (*spinlock_release)(volatile int64_t* spinlock);\n    int32_t (*load32)(volatile int32_t* a);\n    int64_t (*load64)(volatile int64_t* a);\n    void (*memory_fence)(void);\n    void (*test)(void);\n} rt_atomics_if;\n\nextern rt_atomics_if rt_atomics;\n\nrt_end_c\n\n// ______________________________ rt_clipboard.h ______________________________\n\nrt_begin_c\n\ntypedef struct ui_bitmap_s ui_bitmap_t;\n\ntypedef struct {\n    errno_t (*put_text)(const char* s);\n    errno_t (*get_text)(char* text, int32_t* bytes);\n    errno_t (*put_image)(ui_bitmap_t* image); // only for Windows apps\n    void (*test)(void);\n} rt_clipboard_if;\n\nextern rt_clipboard_if rt_clipboard;\n\nrt_end_c\n\n// ________________________________ rt_clock.h ________________________________\n\nrt_begin_c\n\ntypedef struct {\n    int32_t const nsec_in_usec; // nano in micro second\n    int32_t const nsec_in_msec; // nano in milli\n    int32_t const nsec_in_sec;\n    int32_t const usec_in_msec; // micro in milli\n    int32_t const msec_in_sec;  // milli in sec\n    int32_t const usec_in_sec;  // micro in sec\n    fp64_t   (*seconds)(void);      // since boot\n    uint64_t (*nanoseconds)(void);  // since boot overflows in about 584.5 years\n    uint64_t (*unix_microseconds)(void); // since January 1, 1970\n    uint64_t (*unix_seconds)(void);      // since January 1, 1970\n    uint64_t (*microseconds)(void); // NOT monotonic(!) UTC since epoch January 1, 1601\n    uint64_t (*localtime)(void);    // local time microseconds since epoch\n    void (*utc)(uint64_t microseconds, int32_t* year, int32_t* month,\n        int32_t* day, int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms,\n        int32_t* mc);\n    void (*local)(uint64_t microseconds, int32_t* year, int32_t* month,\n        int32_t* day, int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms,\n        int32_t* mc);\n    void (*test)(void);\n} rt_clock_if;\n\nextern rt_clock_if rt_clock;\n\nrt_end_c\n\n\n// _______________________________ rt_config.h ________________________________\n\nrt_begin_c\n\n// Persistent storage for configuration and other small data\n// related to specific application.\n// on Unix-like system ~/.name/key files are used.\n// On Window User registry (could be .dot files/folders).\n// \"name\" is customary basename of \"rt_args.v[0]\"\n\ntypedef struct {\n    errno_t (*save)(const char* name, const char* key,\n                    const void* data, int32_t bytes);\n    int32_t (*size)(const char* name, const char* key);\n    // load() returns number of actual loaded bytes:\n    int32_t (*load)(const char* name, const char* key,\n                    void* data, int32_t bytes);\n    errno_t (*remove)(const char* name, const char* key);\n    errno_t (*clean)(const char* name); // remove all subkeys\n    void (*test)(void);\n} rt_config_if;\n\nextern rt_config_if rt_config;\n\nrt_end_c\n\n\n// ________________________________ rt_core.h _________________________________\n\nrt_begin_c\n\ntypedef struct {\n    errno_t (*err)(void); // errno or GetLastError()\n    void (*set_err)(errno_t err); // errno = err or SetLastError()\n    void (*abort)(void);\n    void (*exit)(int32_t exit_code); // only 8 bits on posix\n    void (*test)(void);\n    struct {                                // posix\n        errno_t const access_denied;        // EACCES\n        errno_t const bad_file;             // EBADF\n        errno_t const broken_pipe;          // EPIPE\n        errno_t const device_not_ready;     // ENXIO\n        errno_t const directory_not_empty;  // ENOTEMPTY\n        errno_t const disk_full;            // ENOSPC\n        errno_t const file_exists;          // EEXIST\n        errno_t const file_not_found;       // ENOENT\n        errno_t const insufficient_buffer;  // E2BIG\n        errno_t const interrupted;          // EINTR\n        errno_t const invalid_data;         // EINVAL\n        errno_t const invalid_handle;       // EBADF\n        errno_t const invalid_parameter;    // EINVAL\n        errno_t const io_error;             // EIO\n        errno_t const more_data;            // ENOBUFS\n        errno_t const name_too_long;        // ENAMETOOLONG\n        errno_t const no_child_process;     // ECHILD\n        errno_t const not_a_directory;      // ENOTDIR\n        errno_t const not_empty;            // ENOTEMPTY\n        errno_t const out_of_memory;        // ENOMEM\n        errno_t const path_not_found;       // ENOENT\n        errno_t const pipe_not_connected;   // EPIPE\n        errno_t const read_only_file;       // EROFS\n        errno_t const resource_deadlock;    // EDEADLK\n        errno_t const too_many_open_files;  // EMFILE\n    } const error;\n} rt_core_if;\n\nextern rt_core_if rt_core;\n\nrt_end_c\n\n// ________________________________ rt_debug.h ________________________________\n\nrt_begin_c\n\n// debug interface essentially is:\n// vfprintf(stderr, format, va)\n// fprintf(stderr, format, ...)\n// with the additional convience:\n// 1. writing to system log (e.g. OutputDebugString() on Windows)\n// 2. always appending \\n at the end of the line and thus flushing buffer\n// Warning: on Windows it is not real-time and subject to 30ms delays\n//          that may or may not happen on some calls\n\ntypedef struct {\n    int32_t level; // global verbosity (interpretation may vary)\n    int32_t const quiet;    // 0\n    int32_t const info;     // 1 basic information (errors and warnings)\n    int32_t const verbose;  // 2 detailed diagnostic\n    int32_t const debug;    // 3 including debug messages\n    int32_t const trace;    // 4 everything (may include nested calls)\n} rt_verbosity_if;\n\ntypedef struct {\n    rt_verbosity_if verbosity;\n    int32_t (*verbosity_from_string)(const char* s);\n    // \"T\" connector for outside write; return false to proceed with default\n    bool (*tee)(const char* s, int32_t count); // return true to intercept\n    void (*output)(const char* s, int32_t count);\n    void (*println_va)(const char* file, int32_t line, const char* func,\n        const char* format, va_list va);\n    void (*println)(const char* file, int32_t line, const char* func,\n        const char* format, ...);\n    void (*perrno)(const char* file, int32_t line,\n        const char* func, int32_t err_no, const char* format, ...);\n    void (*perror)(const char* file, int32_t line,\n        const char* func, int32_t error, const char* format, ...);\n    bool (*is_debugger_present)(void);\n    void (*breakpoint)(void); // no-op if debugger is not present\n    errno_t (*raise)(uint32_t exception);\n    struct  {\n        uint32_t const access_violation;\n        uint32_t const datatype_misalignment;\n        uint32_t const breakpoint;\n        uint32_t const single_step;\n        uint32_t const array_bounds;\n        uint32_t const float_denormal_operand;\n        uint32_t const float_divide_by_zero;\n        uint32_t const float_inexact_result;\n        uint32_t const float_invalid_operation;\n        uint32_t const float_overflow;\n        uint32_t const float_stack_check;\n        uint32_t const float_underflow;\n        uint32_t const int_divide_by_zero;\n        uint32_t const int_overflow;\n        uint32_t const priv_instruction;\n        uint32_t const in_page_error;\n        uint32_t const illegal_instruction;\n        uint32_t const noncontinuable;\n        uint32_t const stack_overflow;\n        uint32_t const invalid_disposition;\n        uint32_t const guard_page;\n        uint32_t const invalid_handle;\n        uint32_t const possible_deadlock;\n    } exception;\n    void (*test)(void);\n} rt_debug_if;\n\n#define rt_println(...) rt_debug.println(__FILE__, __LINE__, __func__, \"\" __VA_ARGS__)\n\nextern rt_debug_if rt_debug;\n\nrt_end_c\n\n// ________________________________ rt_files.h ________________________________\n\nrt_begin_c\n\n// space for \"short\" 260 utf-16 characters filename in utf-8 format:\ntypedef struct rt_file_name_s { char s[1024]; } rt_file_name_t;\n\nenum { rt_files_max_path = (int32_t)sizeof(rt_file_name_t) }; // *)\n\ntypedef struct rt_file_s rt_file_t;\n\ntypedef struct rt_files_stat_s {\n    uint64_t created;\n    uint64_t accessed;\n    uint64_t updated;\n    int64_t  size; // bytes\n    int64_t  type; // device / folder / symlink\n} rt_files_stat_t;\n\ntypedef struct rt_folder_s {\n    uint8_t data[512]; // implementation specific\n} rt_folder_t;\n\ntypedef struct {\n    rt_file_t* const invalid; // (rt_file_t*)-1\n    // rt_files_stat_t.type:\n    int32_t const type_folder;\n    int32_t const type_symlink;\n    int32_t const type_device;\n    // seek() methods:\n    int32_t const seek_set;\n    int32_t const seek_cur;\n    int32_t const seek_end;\n    // open() flags\n    int32_t const o_rd; // read only\n    int32_t const o_wr; // write only\n    int32_t const o_rw; // != (o_rd | o_wr)\n    int32_t const o_append;\n    int32_t const o_create; // opens existing or creates new\n    int32_t const o_excl;   // create fails if file exists\n    int32_t const o_trunc;  // open always truncates to empty\n    int32_t const o_sync;\n    // known folders ids:\n    struct { // known folders:\n        int32_t const home;      // \"c:\\Users\\<username>\" or \"/users/<username>\"\n        int32_t const desktop;\n        int32_t const documents;\n        int32_t const downloads;\n        int32_t const music;\n        int32_t const pictures;\n        int32_t const videos;\n        int32_t const shared;    // \"c:\\Users\\Public\"\n        int32_t const bin;       // \"c:\\Program Files\" aka \"/bin\" aka \"/Applications\"\n        int32_t const data;      // \"c:\\ProgramData\" aka \"/var\" aka \"/data\"\n    } const folder;\n    errno_t (*open)(rt_file_t* *file, const char* filename, int32_t flags);\n    bool    (*is_valid)(rt_file_t* file); // checks both null and invalid\n    errno_t (*seek)(rt_file_t* file, int64_t *position, int32_t method);\n    errno_t (*stat)(rt_file_t* file, rt_files_stat_t* stat, bool follow_symlink);\n    errno_t (*read)(rt_file_t* file, void* data, int64_t bytes, int64_t *transferred);\n    errno_t (*write)(rt_file_t* file, const void* data, int64_t bytes, int64_t *transferred);\n    errno_t (*flush)(rt_file_t* file);\n    void    (*close)(rt_file_t* file);\n    errno_t (*write_fully)(const char* filename, const void* data,\n                           int64_t bytes, int64_t *transferred);\n    bool (*exists)(const char* pathname); // does not guarantee any access writes\n    bool (*is_folder)(const char* pathname);\n    bool (*is_symlink)(const char* pathname);\n    const char* (*basename)(const char* pathname); // c:\\foo\\bar.ext -> bar.ext\n    errno_t (*mkdirs)(const char* pathname); // tries to deep create all folders in pathname\n    errno_t (*rmdirs)(const char* pathname); // tries to remove folder and its subtree\n    errno_t (*create_tmp)(char* file, int32_t count); // create temporary file\n    errno_t (*chmod777)(const char* pathname); // and whole subtree new files and folders\n    errno_t (*symlink)(const char* from, const char* to); // sym link \"ln -s\" **)\n    errno_t (*link)(const char* from, const char* to); // hard link like \"ln\"\n    errno_t (*unlink)(const char* pathname); // delete file or empty folder\n    errno_t (*copy)(const char* from, const char* to); // allows overwriting\n    errno_t (*move)(const char* from, const char* to); // allows overwriting\n    errno_t (*cwd)(char* folder, int32_t count); // get current working dir\n    errno_t (*chdir)(const char* folder); // set working directory\n    const char* (*known_folder)(int32_t kf_id);\n    const char* (*bin)(void);  // Windows: \"c:\\Program Files\" Un*x: \"/bin\"\n    const char* (*data)(void); // Windows: \"c:\\ProgramData\" Un*x: /data or /var\n    const char* (*tmp)(void);  // temporary folder (system or user)\n    // There are better, native, higher performance ways to iterate thru\n    // folders in Posix, Linux and Windows. The following is minimalistic\n    // approach to folder content reading:\n    errno_t (*opendir)(rt_folder_t* folder, const char* folder_name);\n    const char* (*readdir)(rt_folder_t* folder, rt_files_stat_t* optional);\n    void (*closedir)(rt_folder_t* folder);\n    void (*test)(void);\n} rt_files_if;\n\n// *) rt_files_max_path is a compromise - way longer than Windows MAX_PATH of 260\n// and somewhat shorter than 32 * 1024 Windows long path.\n// Use with caution understanding that it is a limitation and where it is\n// important heap may and should be used. Do not to rely on thread stack size\n// (default: 1MB on Windows, Android Linux 64KB, 512 KB  on MacOS, Ubuntu 8MB)\n//\n// **) symlink on Win32 is only allowed in Admin elevated\n//     processes and in Developer mode.\n\nextern rt_files_if rt_files;\n\nrt_end_c\n\n// ______________________________ rt_generics.h _______________________________\n\nrt_begin_c\n\n// Most of ut/ui code is written the way of min(a,b) max(a,b)\n// not having side effects on the arguments and thus evaluating\n// them twice ain't a big deal. However, out of curiosity of\n// usefulness of Generic() in C11 standard here is type-safe\n// single evaluation of the arguments version of what could\n// have been simple minimum and maximum macro definitions.\n// Type safety comes with the cost of complexity in puritan\n// or stoic language like C:\n\nstatic inline int8_t   rt_max_int8(int8_t x, int8_t y)       { return x > y ? x : y; }\nstatic inline int16_t  rt_max_int16(int16_t x, int16_t y)    { return x > y ? x : y; }\nstatic inline int32_t  rt_max_int32(int32_t x, int32_t y)    { return x > y ? x : y; }\nstatic inline int64_t  rt_max_int64(int64_t x, int64_t y)    { return x > y ? x : y; }\nstatic inline uint8_t  rt_max_uint8(uint8_t x, uint8_t y)    { return x > y ? x : y; }\nstatic inline uint16_t rt_max_uint16(uint16_t x, uint16_t y) { return x > y ? x : y; }\nstatic inline uint32_t rt_max_uint32(uint32_t x, uint32_t y) { return x > y ? x : y; }\nstatic inline uint64_t rt_max_uint64(uint64_t x, uint64_t y) { return x > y ? x : y; }\nstatic inline fp32_t   rt_max_fp32(fp32_t x, fp32_t y)       { return x > y ? x : y; }\nstatic inline fp64_t   rt_max_fp64(fp64_t x, fp64_t y)       { return x > y ? x : y; }\n\n// MS cl.exe version 19.39.33523 has issues with \"long\":\n// does not pick up int32_t/uint32_t types for \"long\" and \"unsigned long\"\n// need to handle long / unsigned long separately:\n\nstatic inline long          rt_max_long(long x, long y)                    { return x > y ? x : y; }\nstatic inline unsigned long rt_max_ulong(unsigned long x, unsigned long y) { return x > y ? x : y; }\n\nstatic inline int8_t   rt_min_int8(int8_t x, int8_t y)       { return x < y ? x : y; }\nstatic inline int16_t  rt_min_int16(int16_t x, int16_t y)    { return x < y ? x : y; }\nstatic inline int32_t  rt_min_int32(int32_t x, int32_t y)    { return x < y ? x : y; }\nstatic inline int64_t  rt_min_int64(int64_t x, int64_t y)    { return x < y ? x : y; }\nstatic inline uint8_t  rt_min_uint8(uint8_t x, uint8_t y)    { return x < y ? x : y; }\nstatic inline uint16_t rt_min_uint16(uint16_t x, uint16_t y) { return x < y ? x : y; }\nstatic inline uint32_t rt_min_uint32(uint32_t x, uint32_t y) { return x < y ? x : y; }\nstatic inline uint64_t rt_min_uint64(uint64_t x, uint64_t y) { return x < y ? x : y; }\nstatic inline fp32_t   rt_min_fp32(fp32_t x, fp32_t y)       { return x < y ? x : y; }\nstatic inline fp64_t   rt_min_fp64(fp64_t x, fp64_t y)       { return x < y ? x : y; }\n\nstatic inline long          rt_min_long(long x, long y)                    { return x < y ? x : y; }\nstatic inline unsigned long rt_min_ulong(unsigned long x, unsigned long y) { return x < y ? x : y; }\n\n\nstatic inline void rt_min_undefined(void) { }\nstatic inline void rt_max_undefined(void) { }\n\n#define rt_max(X, Y) _Generic((X) + (Y), \\\n    int8_t:   rt_max_int8,   \\\n    int16_t:  rt_max_int16,  \\\n    int32_t:  rt_max_int32,  \\\n    int64_t:  rt_max_int64,  \\\n    uint8_t:  rt_max_uint8,  \\\n    uint16_t: rt_max_uint16, \\\n    uint32_t: rt_max_uint32, \\\n    uint64_t: rt_max_uint64, \\\n    fp32_t:   rt_max_fp32,   \\\n    fp64_t:   rt_max_fp64,   \\\n    long:     rt_max_long,   \\\n    unsigned long: rt_max_ulong, \\\n    default:  rt_max_undefined)(X, Y)\n\n#define rt_min(X, Y) _Generic((X) + (Y), \\\n    int8_t:   rt_min_int8,   \\\n    int16_t:  rt_min_int16,  \\\n    int32_t:  rt_min_int32,  \\\n    int64_t:  rt_min_int64,  \\\n    uint8_t:  rt_min_uint8,  \\\n    uint16_t: rt_min_uint16, \\\n    uint32_t: rt_min_uint32, \\\n    uint64_t: rt_min_uint64, \\\n    fp32_t:   rt_min_fp32,   \\\n    fp64_t:   rt_min_fp64,   \\\n    long:     rt_min_long,   \\\n    unsigned long: rt_min_ulong, \\\n    default:  rt_min_undefined)(X, Y)\n\n// The expression (X) + (Y) is used in _Generic primarily for type promotion\n// and compatibility between different types of the two operands. In C, when\n// you perform an arithmetic operation like addition between two variables,\n// the types of these variables undergo implicit conversions to a common type\n// according to the usual arithmetic conversions defined in the C standard.\n// This helps ensure that:\n//\n// Type Compatibility: The macro works correctly even if X and Y are of\n// different types. By using (X) + (Y), both X and Y are promoted to a common\n// type, which ensures that the macro selects the appropriate function\n// that can handle this common type.\n//\n// Type Safety: It ensures that the selected function can handle the type\n// resulting from the operation, thereby preventing type mismatches that\n// could lead to undefined behavior or compilation errors.\n\ntypedef struct {\n    void (*test)(void);\n} rt_generics_if;\n\nextern rt_generics_if rt_generics;\n\nrt_end_c\n\n// _______________________________ rt_glyphs.h ________________________________\n\n// Square Four Corners https://www.compart.com/en/unicode/U+26F6\n#define rt_glyph_square_four_corners                    \"\\xE2\\x9B\\xB6\"\n\n// Circled Cross Formee\n// https://codepoints.net/U+1F902\n#define rt_glyph_circled_cross_formee                   \"\\xF0\\x9F\\xA4\\x82\"\n\n// White Large Square https://www.compart.com/en/unicode/U+2B1C\n#define rt_glyph_white_large_square                     \"\\xE2\\xAC\\x9C\"\n\n// N-Ary Times Operator https://www.compart.com/en/unicode/U+2A09\n#define rt_glyph_n_ary_times_operator                   \"\\xE2\\xA8\\x89\"\n\n// Heavy Minus Sign https://www.compart.com/en/unicode/U+2796\n#define rt_glyph_heavy_minus_sign                       \"\\xE2\\x9E\\x96\"\n\n// Heavy Plus Sign https://www.compart.com/en/unicode/U+2795\n#define rt_glyph_heavy_plus_sign                        \"\\xE2\\x9E\\x95\"\n\n// Clockwise Rightwards and Leftwards Open Circle Arrows with Circled One Overlay\n// https://www.compart.com/en/unicode/U+1F502\n// rt_glyph_clockwise_rightwards_and_leftwards_open_circle_arrows_with_circled_one_overlay...\n#define rt_glyph_open_circle_arrows_one_overlay         \"\\xF0\\x9F\\x94\\x82\"\n\n// Halfwidth Katakana-Hiragana Prolonged Sound Mark https://www.compart.com/en/unicode/U+FF70\n#define rt_glyph_prolonged_sound_mark                   \"\\xEF\\xBD\\xB0\"\n\n// Fullwidth Plus Sign https://www.compart.com/en/unicode/U+FF0B\n#define rt_glyph_fullwidth_plus_sign                    \"\\xEF\\xBC\\x8B\"\n\n// Fullwidth Hyphen-Minus https://www.compart.com/en/unicode/U+FF0D\n#define rt_glyph_fullwidth_hyphen_minus                 \"\\xEF\\xBC\\x8D\"\n\n\n// Heavy Multiplication X https://www.compart.com/en/unicode/U+2716\n#define rt_glyph_heavy_multiplication_x                 \"\\xE2\\x9C\\x96\"\n\n// Multiplication Sign https://www.compart.com/en/unicode/U+00D7\n#define rt_glyph_multiplication_sign                    \"\\xC3\\x97\"\n\n// Trigram For Heaven (caption menu button) https://www.compart.com/en/unicode/U+2630\n#define rt_glyph_trigram_for_heaven                     \"\\xE2\\x98\\xB0\"\n\n// (tool bar drag handle like: msvc toolbars)\n// Braille Pattern Dots-12345678  https://www.compart.com/en/unicode/U+28FF\n#define rt_glyph_braille_pattern_dots_12345678          \"\\xE2\\xA3\\xBF\"\n\n// White Square Containing Black Medium Square\n// https://compart.com/en/unicode/U+1F795\n#define rt_glyph_white_square_containing_black_medium_square \"\\xF0\\x9F\\x9E\\x95\"\n\n// White Medium Square\n// https://compart.com/en/unicode/U+25FB\n#define rt_glyph_white_medium_square                   \"\\xE2\\x97\\xBB\"\n\n// White Square with Upper Right Quadrant\n// https://compart.com/en/unicode/U+25F3\n#define rt_glyph_white_square_with_upper_right_quadrant \"\\xE2\\x97\\xB3\"\n\n// White Square with Upper Left Quadrant https://www.compart.com/en/unicode/U+25F0\n#define rt_glyph_white_square_with_upper_left_quadrant \"\\xE2\\x97\\xB0\"\n\n// White Square with Lower Left Quadrant https://www.compart.com/en/unicode/U+25F1\n#define rt_glyph_white_square_with_lower_left_quadrant \"\\xE2\\x97\\xB1\"\n\n// White Square with Lower Right Quadrant https://www.compart.com/en/unicode/U+25F2\n#define rt_glyph_white_square_with_lower_right_quadrant \"\\xE2\\x97\\xB2\"\n\n// White Square with Upper Right Quadrant https://www.compart.com/en/unicode/U+25F3\n#define rt_glyph_white_square_with_upper_right_quadrant \"\\xE2\\x97\\xB3\"\n\n// White Square with Vertical Bisecting Line https://www.compart.com/en/unicode/U+25EB\n#define rt_glyph_white_square_with_vertical_bisecting_line \"\\xE2\\x97\\xAB\"\n\n// (White Square with Horizontal Bisecting Line)\n// Squared Minus https://www.compart.com/en/unicode/U+229F\n#define rt_glyph_squared_minus                          \"\\xE2\\x8A\\x9F\"\n\n// North East and South West Arrow https://www.compart.com/en/unicode/U+2922\n#define rt_glyph_north_east_and_south_west_arrow        \"\\xE2\\xA4\\xA2\"\n\n// South East Arrow to Corner https://www.compart.com/en/unicode/U+21F2\n#define rt_glyph_south_east_white_arrow_to_corner       \"\\xE2\\x87\\xB2\"\n\n// North West Arrow to Corner https://www.compart.com/en/unicode/U+21F1\n#define rt_glyph_north_west_white_arrow_to_corner       \"\\xE2\\x87\\xB1\"\n\n// Leftwards Arrow to Bar https://www.compart.com/en/unicode/U+21E6\n#define rt_glyph_leftwards_white_arrow_to_bar           \"\\xE2\\x87\\xA6\"\n\n// Rightwards Arrow to Bar https://www.compart.com/en/unicode/U+21E8\n#define rt_glyph_rightwards_white_arrow_to_bar          \"\\xE2\\x87\\xA8\"\n\n// Upwards White Arrow https://www.compart.com/en/unicode/U+21E7\n#define rt_glyph_upwards_white_arrow                    \"\\xE2\\x87\\xA7\"\n\n// Downwards White Arrow https://www.compart.com/en/unicode/U+21E9\n#define rt_glyph_downwards_white_arrow                  \"\\xE2\\x87\\xA9\"\n\n// Leftwards White Arrow https://www.compart.com/en/unicode/U+21E4\n#define rt_glyph_leftwards_white_arrow                  \"\\xE2\\x87\\xA4\"\n\n// Rightwards White Arrow https://www.compart.com/en/unicode/U+21E5\n#define rt_glyph_rightwards_white_arrow                 \"\\xE2\\x87\\xA5\"\n\n// Upwards White Arrow on Pedestal https://www.compart.com/en/unicode/U+21EB\n#define rt_glyph_upwards_white_arrow_on_pedestal        \"\\xE2\\x87\\xAB\"\n\n// Braille Pattern Dots-678 https://www.compart.com/en/unicode/U+28E0\n#define rt_glyph_3_dots_tiny_right_bottom_triangle      \"\\xE2\\xA3\\xA0\"\n\n// Braille Pattern Dots-2345678 https://www.compart.com/en/unicode/U+28FE\n// Combining the two into:\n#define rt_glyph_dotted_right_bottom_triangle           \"\\xE2\\xA3\\xA0\\xE2\\xA3\\xBE\"\n\n// Upper Right Drop-Shadowed White Square https://www.compart.com/en/unicode/U+2750\n#define rt_glyph_upper_right_drop_shadowed_white_square \"\\xE2\\x9D\\x90\"\n\n// No-Break Space (NBSP)\n// https://www.compart.com/en/unicode/U+00A0\n#define rt_glyph_nbsp                                   \"\\xC2\\xA0\"\n\n// Word Joiner (WJ)\n// https://compart.com/en/unicode/U+2060\n#define rt_glyph_word_joiner                            \"\\xE2\\x81\\xA0\"\n\n// Zero Width Space (ZWSP)\n// https://compart.com/en/unicode/U+200B\n#define rt_glyph_zwsp                                   \"\\xE2\\x80\\x8B\"\n\n// Zero Width Joiner (ZWJ)\n// https://compart.com/en/unicode/u+200D\n#define rt_glyph_zwj                                    \"\\xE2\\x80\\x8D\"\n\n// En Quad\n// https://compart.com/en/unicode/U+2000\n#define rt_glyph_en_quad \"\\xE2\\x80\\x80\"\n\n// Em Quad\n// https://compart.com/en/unicode/U+2001\n#define rt_glyph_em_quad \"\\xE2\\x80\\x81\"\n\n// En Space\n// https://compart.com/en/unicode/U+2002\n#define rt_glyph_en_space \"\\xE2\\x80\\x82\"\n\n// Em Space\n// https://compart.com/en/unicode/U+2003\n#define rt_glyph_em_space \"\\xE2\\x80\\x83\"\n\n// Hyphen https://www.compart.com/en/unicode/U+2010\n#define rt_glyph_hyphen                                \"\\xE2\\x80\\x90\"\n\n// Non-Breaking Hyphen https://www.compart.com/en/unicode/U+2011\n#define rt_glyph_non_breaking_hyphen                   \"\\xE2\\x80\\x91\"\n\n// Fullwidth Low Line https://www.compart.com/en/unicode/U+FF3F\n#define rt_glyph_fullwidth_low_line                    \"\\xEF\\xBC\\xBF\"\n\n// #define rt_glyph_light_horizontal                     \"\\xE2\\x94\\x80\"\n// Light Horizontal https://www.compart.com/en/unicode/U+2500\n#define rt_glyph_light_horizontal                     \"\\xE2\\x94\\x80\"\n\n// Three-Em Dash https://www.compart.com/en/unicode/U+2E3B\n#define rt_glyph_three_em_dash                         \"\\xE2\\xB8\\xBB\"\n\n// Infinity https://www.compart.com/en/unicode/U+221E\n#define rt_glyph_infinity                              \"\\xE2\\x88\\x9E\"\n\n// Black Large Circle https://www.compart.com/en/unicode/U+2B24\n#define rt_glyph_black_large_circle                    \"\\xE2\\xAC\\xA4\"\n\n// Large Circle https://www.compart.com/en/unicode/U+25EF\n#define rt_glyph_large_circle                          \"\\xE2\\x97\\xAF\"\n\n// Heavy Leftwards Arrow with Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F818\n#define rt_glyph_heavy_leftwards_arrow_with_equilateral_arrowhead           \"\\xF0\\x9F\\xA0\\x98\"\n\n// Heavy Rightwards Arrow with Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81A\n#define rt_glyph_heavy_rightwards_arrow_with_equilateral_arrowhead          \"\\xF0\\x9F\\xA0\\x9A\"\n\n// Heavy Leftwards Arrow with Large Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81C\n#define rt_glyph_heavy_leftwards_arrow_with_large_equilateral_arrowhead     \"\\xF0\\x9F\\xA0\\x9C\"\n\n// Heavy Rightwards Arrow with Large Equilateral Arrowhead https://www.compart.com/en/unicode/U+1F81E\n#define rt_glyph_heavy_rightwards_arrow_with_large_equilateral_arrowhead    \"\\xF0\\x9F\\xA0\\x9E\"\n\n// CJK Unified Ideograph-5973: Kanji Onna \"Female\" https://www.compart.com/en/unicode/U+5973\n#define rt_glyph_kanji_onna_female                                          \"\\xE2\\xBC\\xA5\"\n\n// Leftwards Arrow https://www.compart.com/en/unicode/U+2190\n#define rt_glyph_leftward_arrow                                             \"\\xE2\\x86\\x90\"\n\n// Upwards Arrow https://www.compart.com/en/unicode/U+2191\n#define rt_glyph_upwards_arrow                                              \"\\xE2\\x86\\x91\"\n\n// Rightwards Arrow\n// https://www.compart.com/en/unicode/U+2192\n#define rt_glyph_rightwards_arrow                                           \"\\xE2\\x86\\x92\"\n\n// Downwards Arrow https://www.compart.com/en/unicode/U+2193\n#define rt_glyph_downwards_arrow                                            \"\\xE2\\x86\\x93\"\n\n// Thin Space https://www.compart.com/en/unicode/U+2009\n#define rt_glyph_thin_space                                                 \"\\xE2\\x80\\x89\"\n\n// Medium Mathematical Space (MMSP) https://www.compart.com/en/unicode/U+205F\n#define rt_glyph_mmsp                                                       \"\\xE2\\x81\\x9F\"\n\n// Three-Per-Em Space https://www.compart.com/en/unicode/U+2004\n#define rt_glyph_three_per_em                                               \"\\xE2\\x80\\x84\"\n\n// Six-Per-Em Space https://www.compart.com/en/unicode/U+2006\n#define rt_glyph_six_per_em                                                 \"\\xE2\\x80\\x86\"\n\n// Punctuation Space https://www.compart.com/en/unicode/U+2008\n#define rt_glyph_punctuation                                                \"\\xE2\\x80\\x88\"\n\n// Hair Space https://www.compart.com/en/unicode/U+200A\n#define rt_glyph_hair_space                                                 \"\\xE2\\x80\\x8A\"\n\n// Chinese \"jin4\" https://www.compart.com/en/unicode/U+58F9\n#define rt_glyph_chinese_jin4                                               \"\\xE5\\xA3\\xB9\"\n\n// Chinese \"gong\" https://www.compart.com/en/unicode/U+8D70\n#define rt_glyph_chinese_gong                                                \"\\xE8\\xB5\\xB0\"\n\n// https://www.compart.com/en/unicode/U+1F9F8\n#define rt_glyph_teddy_bear                                                 \"\\xF0\\x9F\\xA7\\xB8\"\n\n// https://www.compart.com/en/unicode/U+1F9CA\n#define rt_glyph_ice_cube                                                   \"\\xF0\\x9F\\xA7\\x8A\"\n\n// Speaker https://www.compart.com/en/unicode/U+1F508\n#define rt_glyph_speaker                                                    \"\\xF0\\x9F\\x94\\x88\"\n\n// Speaker with Cancellation Stroke https://www.compart.com/en/unicode/U+1F507\n#define rt_glyph_mute                                                       \"\\xF0\\x9F\\x94\\x87\"\n\n// TODO: this is used for Font Metric Visualization\n\n// Full Block https://www.compart.com/en/unicode/U+2588\n#define rt_glyph_full_block                             \"\\xE2\\x96\\x88\"\n\n// Black Square https://www.compart.com/en/unicode/U+25A0\n#define rt_glyph_black_square                           \"\\xE2\\x96\\xA0\"\n\n// the appearance of a dragon walking\n// CJK Unified Ideograph-9F98 https://www.compart.com/en/unicode/U+9F98\n#define rt_glyph_walking_dragon                         \"\\xE9\\xBE\\x98\"\n\n// possibly highest \"diacritical marks\" character (Vietnamese)\n// Latin Small Letter U with Horn and Hook Above https://www.compart.com/en/unicode/U+1EED\n#define rt_glyph_u_with_horn_and_hook_above             \"\\xC7\\xAD\"\n\n// possibly \"long descender\" character\n// Latin Small Letter Qp Digraph https://www.compart.com/en/unicode/U+0239\n#define rt_glyph_qp_digraph                             \"\\xC9\\xB9\"\n\n// another possibly \"long descender\" character\n// Cyrillic Small Letter Shha with Descender https://www.compart.com/en/unicode/U+0527\n#define rt_glyph_shha_with_descender                    \"\\xD4\\xA7\"\n\n// a\"very long descender\" character\n// Tibetan Mark Caret Yig Mgo Phur Shad Ma https://www.compart.com/en/unicode/U+0F06\n#define rt_glyph_caret_yig_mgo_phur_shad_ma             \"\\xE0\\xBC\\x86\"\n\n// Tibetan Vowel Sign Vocalic Ll https://www.compart.com/en/unicode/U+0F79\n#define rt_glyph_vocalic_ll                             \"\\xE0\\xBD\\xB9\"\n\n// https://www.compart.com/en/unicode/U+1F4A3\n#define rt_glyph_bomb \"\\xF0\\x9F\\x92\\xA3\"\n\n// https://www.compart.com/en/unicode/U+1F4A1\n#define rt_glyph_electric_light_bulb \"\\xF0\\x9F\\x92\\xA1\"\n\n// https://www.compart.com/en/unicode/U+1F4E2\n#define rt_glyph_public_address_loudspeaker \"\\xF0\\x9F\\x93\\xA2\"\n\n// https://www.compart.com/en/unicode/U+1F517\n#define rt_glyph_link_symbol \"\\xF0\\x9F\\x94\\x97\"\n\n// https://www.compart.com/en/unicode/U+1F571\n#define rt_glyph_black_skull_and_crossbones \"\\xF0\\x9F\\x95\\xB1\"\n\n// https://www.compart.com/en/unicode/U+1F5B5\n#define rt_glyph_screen \"\\xF0\\x9F\\x96\\xB5\"\n\n// https://www.compart.com/en/unicode/U+1F5D7\n#define rt_glyph_overlap \"\\xF0\\x9F\\x97\\x97\"\n\n// https://www.compart.com/en/unicode/U+1F5D6\n#define rt_glyph_maximize \"\\xF0\\x9F\\x97\\x96\"\n\n// https://www.compart.com/en/unicode/U+1F5D5\n#define rt_glyph_minimize \"\\xF0\\x9F\\x97\\x95\"\n\n// Desktop Window\n// https://compart.com/en/unicode/U+1F5D4\n#define rt_glyph_desktop_window \"\\xF0\\x9F\\x97\\x94\"\n\n// https://www.compart.com/en/unicode/U+1F5D9\n#define rt_glyph_cancellation_x \"\\xF0\\x9F\\x97\\x99\"\n\n// https://www.compart.com/en/unicode/U+1F5DF\n#define rt_glyph_page_with_circled_text \"\\xF0\\x9F\\x97\\x9F\"\n\n// https://www.compart.com/en/unicode/U+1F533\n#define rt_glyph_white_square_button \"\\xF0\\x9F\\x94\\xB3\"\n\n// https://www.compart.com/en/unicode/U+1F532\n#define rt_glyph_black_square_button \"\\xF0\\x9F\\x94\\xB2\"\n\n// https://www.compart.com/en/unicode/U+1F5F9\n#define rt_glyph_ballot_box_with_bold_check \"\\xF0\\x9F\\x97\\xB9\"\n\n// https://www.compart.com/en/unicode/U+1F5F8\n#define rt_glyph_light_check_mark \"\\xF0\\x9F\\x97\\xB8\"\n\n// https://compart.com/en/unicode/U+1F4BB\n#define rt_glyph_personal_computer \"\\xF0\\x9F\\x92\\xBB\"\n\n// https://compart.com/en/unicode/U+1F4DC\n#define rt_glyph_desktop_computer \"\\xF0\\x9F\\x93\\x9C\"\n\n// https://compart.com/en/unicode/U+1F4DD\n#define rt_glyph_printer \"\\xF0\\x9F\\x93\\x9D\"\n\n// https://compart.com/en/unicode/U+1F4F9\n#define rt_glyph_video_camera \"\\xF0\\x9F\\x93\\xB9\"\n\n// https://compart.com/en/unicode/U+1F4F8\n#define rt_glyph_camera \"\\xF0\\x9F\\x93\\xB8\"\n\n// https://compart.com/en/unicode/U+1F505\n#define rt_glyph_high_brightness \"\\xF0\\x9F\\x94\\x85\"\n\n// https://compart.com/en/unicode/U+1F506\n#define rt_glyph_low_brightness \"\\xF0\\x9F\\x94\\x86\"\n\n// https://compart.com/en/unicode/U+1F507\n#define rt_glyph_speaker_with_cancellation_stroke \"\\xF0\\x9F\\x94\\x87\"\n\n// https://compart.com/en/unicode/U+1F509\n#define rt_glyph_speaker_with_one_sound_wave \"\\xF0\\x9F\\x94\\x89\"\n\n// Right-Pointing Magnifying Glass\n// https://compart.com/en/unicode/U+1F50E\n#define rt_glyph_right_pointing_magnifying_glass \"\\xF0\\x9F\\x94\\x8E\"\n\n// Radio Button\n// https://compart.com/en/unicode/U+1F518\n#define rt_glyph_radio_button \"\\xF0\\x9F\\x94\\x98\"\n\n// https://compart.com/en/unicode/U+1F525\n#define rt_glyph_fire \"\\xF0\\x9F\\x94\\xA5\"\n\n// Gear\n// https://compart.com/en/unicode/U+2699\n#define rt_glyph_gear \"\\xE2\\x9A\\x99\"\n\n// Nut and Bolt\n// https://compart.com/en/unicode/U+1F529\n#define rt_glyph_nut_and_bolt \"\\xF0\\x9F\\x94\\xA9\"\n\n// Hammer and Wrench\n// https://compart.com/en/unicode/U+1F6E0\n#define rt_glyph_hammer_and_wrench \"\\xF0\\x9F\\x9B\\xA0\"\n\n// https://compart.com/en/unicode/U+1F53E\n#define rt_glyph_upwards_button \"\\xF0\\x9F\\x94\\xBE\"\n\n// https://compart.com/en/unicode/U+1F53F\n#define rt_glyph_downwards_button \"\\xF0\\x9F\\x94\\xBF\"\n\n// https://compart.com/en/unicode/U+1F5C7\n#define rt_glyph_litter_in_bin_sign \"\\xF0\\x9F\\x97\\x87\"\n\n// Checker Board\n// https://compart.com/en/unicode/U+1F67E\n#define rt_glyph_checker_board \"\\xF0\\x9F\\x9A\\xBE\"\n\n// Reverse Checker Board\n// https://compart.com/en/unicode/U+1F67F\n#define rt_glyph_reverse_checker_board \"\\xF0\\x9F\\x9A\\xBF\"\n\n// Clipboard\n// https://compart.com/en/unicode/U+1F4CB\n#define rt_glyph_clipboard \"\\xF0\\x9F\\x93\\x8B\"\n\n// Two Joined Squares https://www.compart.com/en/unicode/U+29C9\n#define rt_glyph_two_joined_squares \"\\xE2\\xA7\\x89\"\n\n// White Heavy Check Mark\n// https://compart.com/en/unicode/U+2705\n#define rt_glyph_white_heavy_check_mark \"\\xE2\\x9C\\x85\"\n\n// Negative Squared Cross Mark\n// https://compart.com/en/unicode/U+274E\n#define rt_glyph_negative_squared_cross_mark \"\\xE2\\x9D\\x8E\"\n\n// Lower Right Drop-Shadowed White Square\n// https://compart.com/en/unicode/U+274F\n#define rt_glyph_lower_right_drop_shadowed_white_square \"\\xE2\\x9D\\x8F\"\n\n// Upper Right Drop-Shadowed White Square\n// https://compart.com/en/unicode/U+2750\n#define rt_glyph_upper_right_drop_shadowed_white_square \"\\xE2\\x9D\\x90\"\n\n// Lower Right Shadowed White Square\n// https://compart.com/en/unicode/U+2751\n#define rt_glyph_lower_right_shadowed_white_square \"\\xE2\\x9D\\x91\"\n\n// Upper Right Shadowed White Square\n// https://compart.com/en/unicode/U+2752\n#define rt_glyph_upper_right_shadowed_white_square \"\\xE2\\x9D\\x92\"\n\n// Left Double Wiggly Fence\n// https://compart.com/en/unicode/U+29DA\n#define rt_glyph_left_double_wiggly_fence \"\\xE2\\xA7\\x9A\"\n\n// Right Double Wiggly Fence\n// https://compart.com/en/unicode/U+29DB\n#define rt_glyph_right_double_wiggly_fence \"\\xE2\\xA7\\x9B\"\n\n// Logical Or\n// https://compart.com/en/unicode/U+2228\n#define rt_glyph_logical_or \"\\xE2\\x88\\xA8\"\n\n// Logical And\n// https://compart.com/en/unicode/U+2227\n#define rt_glyph_logical_and \"\\xE2\\x88\\xA7\"\n\n// Double Vertical Bar (Pause)\n// https://compart.com/en/unicode/U+23F8\n#define rt_glyph_double_vertical_bar \"\\xE2\\x8F\\xB8\"\n\n// Black Square For Stop\n// https://compart.com/en/unicode/U+23F9\n#define rt_glyph_black_square_for_stop \"\\xE2\\x8F\\xB9\"\n\n// Black Circle For Record\n// https://compart.com/en/unicode/U+23FA\n#define rt_glyph_black_circle_for_record \"\\xE2\\x8F\\xBA\"\n\n// Negative Squared Latin Capital Letter \"I\"\n// https://compart.com/en/unicode/U+1F158\n#define rt_glyph_negative_squared_latin_capital_letter_i \"\\xF0\\x9F\\x85\\x98\"\n#define rt_glyph_info rt_glyph_negative_squared_latin_capital_letter_i\n\n// Circled Information Source\n// https://compart.com/en/unicode/U+1F6C8\n#define rt_glyph_circled_information_source \"\\xF0\\x9F\\x9B\\x88\"\n\n// Information Source\n// https://compart.com/en/unicode/U+2139\n#define rt_glyph_information_source \"\\xE2\\x84\\xB9\"\n\n// Squared Cool\n// https://compart.com/en/unicode/U+1F192\n#define rt_glyph_squared_cool \"\\xF0\\x9F\\x86\\x92\"\n\n// Squared OK\n// https://compart.com/en/unicode/U+1F197\n#define rt_glyph_squared_ok \"\\xF0\\x9F\\x86\\x97\"\n\n// Squared Free\n// https://compart.com/en/unicode/U+1F193\n#define rt_glyph_squared_free \"\\xF0\\x9F\\x86\\x93\"\n\n// Squared New\n// https://compart.com/en/unicode/U+1F195\n#define rt_glyph_squared_new \"\\xF0\\x9F\\x86\\x95\"\n\n// Lady Beetle\n// https://compart.com/en/unicode/U+1F41E\n#define rt_glyph_lady_beetle \"\\xF0\\x9F\\x90\\x9E\"\n\n// Brain\n// https://compart.com/en/unicode/U+1F9E0\n#define rt_glyph_brain \"\\xF0\\x9F\\xA7\\xA0\"\n\n// South West Arrow with Hook\n// https://www.compart.com/en/unicode/U+2926\n#define rt_glyph_south_west_arrow_with_hook \"\\xE2\\xA4\\xA6\"\n\n// North West Arrow with Hook\n// https://www.compart.com/en/unicode/U+2923\n#define rt_glyph_north_west_arrow_with_hook \"\\xE2\\xA4\\xA3\"\n\n// White Sun with Rays\n// https://www.compart.com/en/unicode/U+263C\n#define rt_glyph_white_sun_with_rays \"\\xE2\\x98\\xBC\"\n\n// Black Sun with Rays\n// https://www.compart.com/en/unicode/U+2600\n#define rt_glyph_black_sun_with_rays \"\\xE2\\x98\\x80\"\n\n// Sun Behind Cloud\n// https://www.compart.com/en/unicode/U+26C5\n#define rt_glyph_sun_behind_cloud \"\\xE2\\x9B\\x85\"\n\n// White Sun\n// https://www.compart.com/en/unicode/U+1F323\n#define rt_glyph_white_sun \"\\xF0\\x9F\\x8C\\xA3\"\n\n// Crescent Moon\n// https://www.compart.com/en/unicode/U+1F319\n#define rt_glyph_crescent_moon \"\\xF0\\x9F\\x8C\\x99\"\n\n// Latin Capital Letter E with Cedilla and Breve\n// https://compart.com/en/unicode/U+1E1C\n#define rt_glyph_E_with_cedilla_and_breve \"\\xE1\\xB8\\x9C\"\n\n// Box Drawings Heavy Vertical and Horizontal\n// https://compart.com/en/unicode/U+254B\n#define rt_glyph_box_drawings_heavy_vertical_and_horizontal \"\\xE2\\x95\\x8B\"\n\n// Box Drawings Light Diagonal Cross\n// https://compart.com/en/unicode/U+2573\n#define rt_glyph_box_drawings_light_diagonal_cross \"\\xE2\\x95\\xB3\"\n\n// Combining Enclosing Square\n// https://compart.com/en/unicode/U+20DE\n#define rt_glyph_combining_enclosing_square \"\\xE2\\x83\\x9E\"\n\n// Combining Enclosing Screen\n// https://compart.com/en/unicode/U+20E2\n#define rt_glyph_combining_enclosing_screen \"\\xE2\\x83\\xA2\"\n\n// Combining Enclosing Keycap\n// https://compart.com/en/unicode/U+20E3\n#define rt_glyph_combining_enclosing_keycap \"\\xE2\\x83\\xA3\"\n\n// Combining Enclosing Circle\n// https://compart.com/en/unicode/U+20DD\n#define rt_glyph_combining_enclosing_circle \"\\xE2\\x83\\x9D\"\n\n// Frame with Picture\n// https://compart.com/en/unicode/U+1F5BC\n#define rt_glyph_frame_with_picture \"\\xF0\\x9F\\x96\\xBC\"\n// with emoji variation selector: \"\\xF0\\x9F\\x96\\xBC\\xEF\\xB8\\x8F\"\n\n// Document with Picture\n// https://compart.com/en/unicode/U+1F5BB\n#define rt_glyph_document_with_picture \"\\xF0\\x9F\\x96\\xBB\"\n\n// Frame with Tiles\n// https://compart.com/en/unicode/U+1F5BD\n#define rt_glyph_frame_with_tiles \"\\xF0\\x9F\\x96\\xBD\"\n\n// Frame with an X\n// https://compart.com/en/unicode/U+1F5BE\n#define rt_glyph_frame_with_an_x \"\\xF0\\x9F\\x96\\xBE\"\n\n// Left Right Arrow\n// https://compart.com/en/unicode/U+2194\n#define rt_glyph_left_right_arrow \"\\xE2\\x86\\x94\"\n\n// Up Down Arrow\n// https://compart.com/en/unicode/U+2195\n#define rt_glyph_up_down_arrow \"\\xE2\\x86\\x95\"\n\n// ________________________________ rt_heap.h _________________________________\n\nrt_begin_c\n\n// It is absolutely OK to use posix compliant\n// malloc()/calloc()/realloc()/free() function calls with understanding\n// that they introduce serialization points in multi-threaded applications\n// and may be induce wait states that under pressure (all cores busy) may\n// result in prolonged wait which may not be acceptable for real time\n// processing pipelines.\n//\n// heap_if.functions may or may not be faster than malloc()/free() ...\n//\n// Some callers may find realloc() parameters more convenient to avoid\n// anti-pattern\n//      void* reallocated = realloc(p, new_size);\n//      if (reallocated != null) { p = reallocated; }\n// and avoid never ending discussion of legality and implementation\n// compliance of the situation:\n//      realloc(p /* when p == null */, ...)\n//\n// zero: true initializes allocated or reallocated tail memory to 0x00\n// be careful with zeroing heap memory. It will result in virtual\n// to physical memory mapping and may be expensive.\n\ntypedef struct rt_heap_s rt_heap_t;\n\ntypedef struct { // heap == null uses process serialized LFH\n    errno_t (*alloc)(void* *a, int64_t bytes);\n    errno_t (*alloc_zero)(void* *a, int64_t bytes);\n    errno_t (*realloc)(void* *a, int64_t bytes);\n    errno_t (*realloc_zero)(void* *a, int64_t bytes);\n    void    (*free)(void* a);\n    // heaps:\n    rt_heap_t* (*create)(bool serialized);\n    errno_t (*allocate)(rt_heap_t* heap, void* *a, int64_t bytes, bool zero);\n    // reallocate may return ERROR_OUTOFMEMORY w/o changing 'a' *)\n    errno_t (*reallocate)(rt_heap_t* heap, void* *a, int64_t bytes, bool zero);\n    void    (*deallocate)(rt_heap_t* heap, void* a);\n    int64_t (*bytes)(rt_heap_t* heap, void* a); // actual allocated size\n    void    (*dispose)(rt_heap_t* heap);\n    void    (*test)(void);\n} rt_heap_if;\n\nextern rt_heap_if rt_heap;\n\n// *) zero in reallocate applies to the newly appended bytes\n\n// On Windows rt_mem.heap is based on serialized LFH returned by GetProcessHeap()\n// https://learn.microsoft.com/en-us/windows/win32/memory/low-fragmentation-heap\n// threads can benefit from not serialized, not LFH if they allocate and free\n// memory in time critical loops.\n\nrt_end_c\n\n\n// _______________________________ rt_loader.h ________________________________\n\nrt_begin_c\n\n// see:\n// https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html\n\ntypedef struct {\n    // mode:\n    int32_t const local;\n    int32_t const lazy;\n    int32_t const now;\n    int32_t const global;\n    // \"If the value of file is null, dlopen() provides a handle on a global\n    //  symbol object.\" posix\n    void* (*open)(const char* filename, int32_t mode);\n    void* (*sym)(void* handle, const char* name);\n    void  (*close)(void* handle);\n    void (*test)(void);\n} rt_loader_if;\n\nextern rt_loader_if rt_loader;\n\nrt_end_c\n\n// _________________________________ rt_mem.h _________________________________\n\nrt_begin_c\n\ntypedef struct {\n    // whole file read only\n    errno_t (*map_ro)(const char* filename, void** data, int64_t* bytes);\n    // whole file read-write\n    errno_t (*map_rw)(const char* filename, void** data, int64_t* bytes);\n    void (*unmap)(void* data, int64_t bytes);\n    // map_resource() maps data from resources, do NOT unmap!\n    errno_t  (*map_resource)(const char* label, void** data, int64_t* bytes);\n    int32_t (*page_size)(void); // 4KB or 64KB on Windows\n    int32_t (*large_page_size)(void);  // 2MB on Windows\n    // allocate() contiguous reserved virtual address range,\n    // if possible committed to physical memory.\n    // Memory guaranteed to be aligned to page boundary.\n    // Memory is guaranteed to be initialized to zero on access.\n    void* (*allocate)(int64_t bytes_multiple_of_page_size);\n    void  (*deallocate)(void* a, int64_t bytes_multiple_of_page_size);\n    void  (*test)(void);\n} rt_mem_if;\n\nextern rt_mem_if rt_mem;\n\nrt_end_c\n\n\n// _________________________________ rt_nls.h _________________________________\n\nrt_begin_c\n\ntypedef struct { // i18n national language support\n    void (*init)(void);\n    const char* (*locale)(void);  // \"en-US\" \"zh-CN\" etc...\n    // force locale for debugging and testing:\n    errno_t (*set_locale)(const char* locale); // only for calling thread\n    // nls(s) is same as string(strid(s), s)\n    const char* (*str)(const char* defau1t); // returns localized string\n    // strid(\"foo\") returns -1 if there is no matching\n    // ENGLISH NEUTRAL STRINGTABLE entry\n    int32_t (*strid)(const char* s);\n    // given strid > 0 returns localized string or default value\n    const char* (*string)(int32_t strid, const char* defau1t);\n} rt_nls_if;\n\nextern rt_nls_if rt_nls;\n\nrt_end_c\n\n// _________________________________ rt_num.h _________________________________\n\nrt_begin_c\n\ntypedef struct {\n    uint64_t lo;\n    uint64_t hi;\n} rt_num128_t; // uint128_t may be supported by compiler\n\ntypedef struct {\n    rt_num128_t (*add128)(const rt_num128_t a, const rt_num128_t b);\n    rt_num128_t (*sub128)(const rt_num128_t a, const rt_num128_t b);\n    rt_num128_t (*mul64x64)(uint64_t a, uint64_t b);\n    uint64_t (*muldiv128)(uint64_t a, uint64_t b, uint64_t d);\n    uint32_t (*gcd32)(uint32_t u, uint32_t v); // greatest common denominator\n    // non-crypto strong pseudo-random number generators (thread safe)\n    uint32_t (*random32)(uint32_t *state); // \"Mulberry32\"\n    uint64_t (*random64)(uint64_t *state); // \"Trust\"\n    // \"FNV-1a\" hash functions (if bytes == 0 expects zero terminated string)\n    uint32_t (*hash32)(const char* s, int64_t bytes);\n    uint64_t (*hash64)(const char* s, int64_t bytes);\n    void     (*test)(void);\n} rt_num_if;\n\nextern rt_num_if rt_num;\n\nrt_end_c\n\n\n// _______________________________ rt_static.h ________________________________\n\nrt_begin_c\n\n// rt_static_init(unique_name) { code_to_execute_before_main }\n\n#if defined(_MSC_VER)\n\n#if defined(_WIN64) || defined(_M_X64)\n#define _msvc_symbol_prefix_ \"\"\n#else\n#define _msvc_symbol_prefix_ \"_\"\n#endif\n\n#pragma comment(linker, \"/include:rt_force_symbol_reference\")\n\nvoid* rt_force_symbol_reference(void* symbol);\n\n#define _msvc_ctor_(sym_prefix, func)                                    \\\n  void func(void);                                                        \\\n  int32_t (* rt_array ## func)(void);                                     \\\n  int32_t func ## _wrapper(void);                                         \\\n  int32_t func ## _wrapper(void) { func();                                \\\n  rt_force_symbol_reference((void*)rt_array ## func);                     \\\n  rt_force_symbol_reference((void*)func ## _wrapper); return 0; }         \\\n  extern int32_t (* rt_array ## func)(void);                              \\\n  __pragma(comment(linker, \"/include:\" sym_prefix # func \"_wrapper\"))     \\\n  __pragma(section(\".CRT$XCU\", read))                                     \\\n  __declspec(allocate(\".CRT$XCU\"))                                        \\\n    int32_t (* rt_array ## func)(void) = func ## _wrapper;\n\n#define rt_static_init2_(func, line) _msvc_ctor_(_msvc_symbol_prefix_, \\\n    func ## _constructor_##line)                                       \\\n    void func ## _constructor_##line(void)\n\n#define rt_static_init1_(func, line) rt_static_init2_(func, line)\n\n#define rt_static_init(func) rt_static_init1_(func, __LINE__)\n\n#else\n#define rt_static_init(n) __attribute__((constructor)) \\\n        static void _init_ ## n ## __LINE__ ## _ctor(void)\n#endif\n\nvoid rt_static_init_test(void);\n\nrt_end_c\n\n// _______________________________ rt_streams.h _______________________________\n\nrt_begin_c\n\ntypedef struct rt_stream_if rt_stream_if;\n\ntypedef struct rt_stream_if {\n    errno_t (*read)(rt_stream_if* s, void* data, int64_t bytes,\n                    int64_t *transferred);\n    errno_t (*write)(rt_stream_if* s, const void* data, int64_t bytes,\n                     int64_t *transferred);\n    void    (*close)(rt_stream_if* s); // optional\n} rt_stream_if;\n\ntypedef struct {\n    rt_stream_if   stream;\n    const void* data_read;\n    int64_t     bytes_read;\n    int64_t     pos_read;\n    void*       data_write;\n    int64_t     bytes_write;\n    int64_t     pos_write;\n} rt_stream_memory_if;\n\ntypedef struct {\n    void (*read_only)(rt_stream_memory_if* s,  const void* data, int64_t bytes);\n    void (*write_only)(rt_stream_memory_if* s, void* data, int64_t bytes);\n    void (*read_write)(rt_stream_memory_if* s, const void* read, int64_t read_bytes,\n                                               void* write, int64_t write_bytes);\n    void (*test)(void);\n} rt_streams_if;\n\nextern rt_streams_if rt_streams;\n\nrt_end_c\n\n// ______________________________ rt_processes.h ______________________________\n\nrt_begin_c\n\ntypedef struct {\n    const char* command;\n    rt_stream_if* in;\n    rt_stream_if* out;\n    rt_stream_if* err;\n    uint32_t exit_code;\n    fp64_t   timeout; // seconds\n} rt_processes_child_t;\n\n// Process name may be an the executable filename with\n// full, partial or absent pathname.\n// Case insensitive on Windows.\n\ntypedef struct {\n    const char* (*name)(void); // argv[0] like but full path\n    uint64_t  (*pid)(const char* name); // 0 if process not found\n    errno_t   (*pids)(const char* name, uint64_t* pids/*[size]*/, int32_t size,\n                      int32_t *count); // return 0, error or ERROR_MORE_DATA\n    errno_t   (*nameof)(uint64_t pid, char* name, int32_t count); // pathname\n    bool      (*present)(uint64_t pid);\n    errno_t   (*kill)(uint64_t pid, fp64_t timeout_seconds);\n    errno_t   (*kill_all)(const char* name, fp64_t timeout_seconds);\n    bool      (*is_elevated)(void); // Is process running as root/ Admin / System?\n    errno_t   (*restart_elevated)(void); // retuns error or exits on success\n    errno_t   (*run)(rt_processes_child_t* child);\n    errno_t   (*popen)(const char* command, int32_t *xc, rt_stream_if* output,\n                       fp64_t timeout_seconds); // <= 0 infinite\n    // popen() does NOT guarantee stream zero termination on errors\n    errno_t  (*spawn)(const char* command); // spawn fully detached process\n    void (*test)(void);\n} rt_processes_if;\n\nextern rt_processes_if rt_processes;\n\nrt_end_c\n\n// _______________________________ rt_threads.h _______________________________\n\nrt_begin_c\n\ntypedef struct rt_event_s* rt_event_t;\n\ntypedef struct {\n    rt_event_t (*create)(void); // never returns null\n    rt_event_t (*create_manual)(void); // never returns null\n    void (*set)(rt_event_t e);\n    void (*reset)(rt_event_t e);\n    void (*wait)(rt_event_t e);\n    // returns 0 on success or -1 on timeout\n    int32_t (*wait_or_timeout)(rt_event_t e, fp64_t seconds); // seconds < 0 forever\n    // returns event index or -1 on timeout or -2 on abandon\n    int32_t (*wait_any)(int32_t n, rt_event_t events[]); // -1 on abandon\n    int32_t (*wait_any_or_timeout)(int32_t n, rt_event_t e[], fp64_t seconds);\n    void (*dispose)(rt_event_t e);\n    void (*test)(void);\n} rt_event_if;\n\nextern rt_event_if rt_event;\n\ntypedef struct rt_aligned_8 rt_mutex_s { uint8_t content[40]; } rt_mutex_t;\n\ntypedef struct {\n    void (*init)(rt_mutex_t* m);\n    void (*lock)(rt_mutex_t* m);\n    void (*unlock)(rt_mutex_t* m);\n    void (*dispose)(rt_mutex_t* m);\n    void (*test)(void);\n} rt_mutex_if;\n\nextern rt_mutex_if rt_mutex;\n\ntypedef struct thread_s* rt_thread_t;\n\ntypedef struct {\n    rt_thread_t (*start)(void (*func)(void*), void* p); // never returns null\n    errno_t     (*join)(rt_thread_t thread, fp64_t timeout_seconds); // < 0 forever\n    void        (*detach)(rt_thread_t thread); // closes handle. thread is not joinable\n    void        (*name)(const char* name); // names the thread\n    void        (*realtime)(void); // bumps calling thread priority\n    void        (*yield)(void);    // pthread_yield() / Win32: SwitchToThread()\n    void        (*sleep_for)(fp64_t seconds);\n    uint64_t    (*id_of)(rt_thread_t t);\n    uint64_t    (*id)(void); // gettid()\n    rt_thread_t (*self)(void); // Pseudo Handle may differ in access to .open(.id())\n    errno_t     (*open)(rt_thread_t* t, uint64_t id);\n    void        (*close)(rt_thread_t t);\n    void        (*test)(void);\n} rt_thread_if;\n\nextern rt_thread_if rt_thread;\n\nrt_end_c\n\n// ________________________________ rt_vigil.h ________________________________\n\nrt_begin_c\n\n// better rt_assert() - augmented with printf format and parameters\n// rt_swear() - release configuration rt_assert() in honor of:\n// https://github.com/munificent/vigil\n\n#define rt_static_assertion(condition) static_assert(condition, #condition)\n\ntypedef struct {\n    int32_t (*failed_assertion)(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...);\n    int32_t (*fatal_termination)(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...);\n    int32_t (*fatal_if_error)(const char* file, int32_t line, const char* func,\n        const char* condition, errno_t r, const char* format, ...);\n    void (*test)(void);\n} rt_vigil_if;\n\nextern rt_vigil_if rt_vigil;\n\n#if defined(DEBUG)\n  #define rt_assert(b, ...) rt_suppress_constant_cond_exp           \\\n    /* const cond */                                                \\\n    (void)((!!(b)) || rt_vigil.failed_assertion(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n#else\n  #define rt_assert(b, ...) ((void)0)\n#endif\n\n// rt_swear() is runtime rt_assert() for both debug and release configurations\n\n#define rt_swear(b, ...) rt_suppress_constant_cond_exp                 \\\n    /* const cond */                                                \\\n    (void)((!!(b)) || rt_vigil.failed_assertion(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_fatal(...) (void)(rt_vigil.fatal_termination(            \\\n    __FILE__, __LINE__,  __func__, \"\",  \"\" __VA_ARGS__))\n\n#define rt_fatal_if(b, ...) rt_suppress_constant_cond_exp           \\\n    /* const cond */                                                \\\n    (void)((!(b)) || rt_vigil.fatal_termination(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_fatal_if_not(b, ...) rt_suppress_constant_cond_exp        \\\n    /* const cond */                                                 \\\n    (void)((!!(b)) || rt_vigil.fatal_termination(__FILE__, __LINE__, \\\n    __func__, #b, \"\" __VA_ARGS__))\n\n#define rt_not_null(e, ...) rt_fatal_if((e) == null, \"\" __VA_ARGS__)\n\n#define rt_fatal_if_error(r, ...) rt_suppress_constant_cond_exp      \\\n    /* const cond */                                                 \\\n    (void)(rt_vigil.fatal_if_error(__FILE__, __LINE__, __func__,     \\\n                                   #r, r, \"\" __VA_ARGS__))\n\n#define rt_fatal_win32err(c, ...) rt_suppress_constant_cond_exp      \\\n    /* const cond */                                                 \\\n    (void)(rt_vigil.fatal_if_error(__FILE__, __LINE__, __func__,     \\\n                                   #c, rt_b2e(c), \"\" __VA_ARGS__))\n\nrt_end_c\n// ___________________________________ rt.h ___________________________________\n\n// the rest is in alphabetical order (no inter dependencies)\n\n// ________________________________ rt_work.h _________________________________\n\nrt_begin_c\n\n// Minimalistic \"react\"-like work_queue or work items and\n// a thread based workers. See rt_worker_test() for usage.\n\ntypedef struct rt_event_s*     rt_event_t;\ntypedef struct rt_work_s       rt_work_t;\ntypedef struct rt_work_queue_s rt_work_queue_t;\n\ntypedef struct rt_work_s {\n    rt_work_queue_t* queue; // queue where the call is or was last scheduled\n    fp64_t when;       // proc() call will be made after or at this time\n    void (*work)(rt_work_t* c);\n    void*  data;       // extra data that will be passed to proc() call\n    rt_event_t  done;  // if not null signalled after calling proc() or canceling\n    rt_work_t*  next;  // next element in the queue (implementation detail)\n    bool    canceled;  // set to true inside .cancel() call\n} rt_work_t;\n\ntypedef struct rt_work_queue_s {\n    rt_work_t* head;\n    int64_t    lock; // spinlock\n    rt_event_t changed; // if not null will be signaled when head changes\n} rt_work_queue_t;\n\ntypedef struct rt_work_queue_if {\n    void (*post)(rt_work_t* c);\n    bool (*get)(rt_work_queue_t*, rt_work_t* *c);\n    void (*call)(rt_work_t* c);\n    void (*dispatch)(rt_work_queue_t* q); // all ready messages\n    void (*cancel)(rt_work_t* c);\n    void (*flush)(rt_work_queue_t* q); // cancel all requests in the queue\n} rt_work_queue_if;\n\nextern rt_work_queue_if  rt_work_queue;\n\ntypedef struct rt_worker_s {\n    rt_work_queue_t queue;\n    rt_thread_t     thread;\n    rt_event_t      wake;\n    volatile bool   quit;\n} rt_worker_t;\n\ntypedef struct rt_worker_if {\n    void    (*start)(rt_worker_t* tq);\n    void    (*post)(rt_worker_t* tq, rt_work_t* w);\n    errno_t (*join)(rt_worker_t* tq, fp64_t timeout);\n    void    (*test)(void);\n} rt_worker_if;\n\nextern rt_worker_if rt_worker;\n\n// worker thread waits for a queue's `wake` event with the timeout\n// infinity or if queue is not empty delta time till the head\n// item of the queue.\n//\n// Upon post() call the `wake` event is set and the worker thread\n// wakes up and dispatches all the items with .when less then now\n// calling function work() if it is not null and optionally signaling\n// .done event if it is not null.\n//\n// When all ready items in the queue are processed worker thread locks\n// the queue and if the head is present calculates next timeout based\n// on .when time of the head or sets timeout to infinity if the queue\n// is empty.\n//\n// Function .join() sets .quit to true signals .wake event and attempt\n// to join the worker .thread with specified timeout.\n// It is the responsibility of the caller to ensure that no other\n// work is posted after calling .join() because it will be lost.\n\nrt_end_c\n\n/*\n    Usage examples:\n\n    // The dispatch_until() is just for testing purposes.\n    // Usually rt_work_queue.dispatch(q) will be called inside each\n    // iteration of message loop of a dispatch [UI] thread.\n\n    static void dispatch_until(rt_work_queue_t* q, int32_t* i, const int32_t n);\n\n    // simple way of passing a single pointer to call_later\n\n    static void every_100ms(rt_work_t* w) {\n        int32_t* i = (int32_t*)w->data;\n        rt_println(\"i: %d\", *i);\n        (*i)++;\n        w->when = rt_clock.seconds() + 0.100;\n        rt_work_queue.post(w);\n    }\n\n    static void example_1(void) {\n        rt_work_queue_t queue = {0};\n        // if a single pointer will suffice\n        int32_t i = 0;\n        rt_work_t work = {\n            .queue = &queue,\n            .when  = rt_clock.seconds() + 0.100,\n            .work  = every_100ms,\n            .data  = &i\n        };\n        rt_work_queue.post(&work);\n        dispatch_until(&queue, &i, 4);\n    }\n\n    // extending rt_work_t with extra data:\n\n    typedef struct rt_work_ex_s {\n        union {\n            rt_work_t base;\n            struct rt_work_s;\n        };\n        struct { int32_t a; int32_t b; } s;\n        int32_t i;\n    } rt_work_ex_t;\n\n    static void every_200ms(rt_work_t* w) {\n        rt_work_ex_t* ex = (rt_work_ex_t*)w;\n        rt_println(\"ex { .i: %d, .s.a: %d .s.b: %d}\", ex->i, ex->s.a, ex->s.b);\n        ex->i++;\n        const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n        w->when = rt_clock.seconds() + 0.200;\n        rt_work_queue.post(w);\n    }\n\n    static void example_2(void) {\n        rt_work_queue_t queue = {0};\n        rt_work_ex_t work = {\n            .queue = &queue,\n            .when  = rt_clock.seconds() + 0.200,\n            .work  = every_200ms,\n            .data  = null,\n            .s = { .a = 1, .b = 2 },\n            .i = 0\n        };\n        rt_work_queue.post(&work.base);\n        dispatch_until(&queue, &work.i, 4);\n    }\n\n    static void dispatch_until(rt_work_queue_t* q, int32_t* i, const int32_t n) {\n        while (q->head != null && *i < n) {\n            rt_thread.sleep_for(0.0001); // 100 microseconds\n            rt_work_queue.dispatch(q);\n        }\n        rt_work_queue.flush(q);\n    }\n\n    // worker:\n\n    static void do_work(rt_work_t* w) {\n        // TODO: something useful\n    }\n\n    static void worker_test(void) {\n        rt_worker_t worker = { 0 };\n        rt_worker.start(&worker);\n        rt_work_t work = {\n            .when  = rt_clock.seconds() + 0.010, // 10ms\n            .done  = rt_event.create(),\n            .work  = do_work\n        };\n        rt_worker.post(&worker, &work);\n        rt_event.wait(work.done);    // await(work)\n        rt_event.dispose(work.done); // responsibility of the caller\n        rt_fatal_if_error(rt_worker.join(&worker, -1.0));\n    }\n\n    // Hint:\n    // To monitor timing turn on MSVC Output / Show Timestamp (clock button)\n\n*/\n\n#endif // rt_definition\n\n#ifdef rt_implementation\n// ________________________________ rt_win32.h ________________________________\n\n#ifdef WIN32\n\n#pragma warning(push)\n#pragma warning(disable: 4255) // no function prototype: '()' to '(void)'\n#pragma warning(disable: 4459) // declaration of '...' hides global declaration\n\n#pragma push_macro(\"UNICODE\")\n#define UNICODE // always because otherwise IME does not work\n\n// ut:\n#include <Windows.h>  // used by:\n#include <Psapi.h>    // both rt_loader.c and rt_processes.c\n#include <shellapi.h> // rt_processes.c\n#include <winternl.h> // rt_processes.c\n#include <initguid.h>     // for knownfolders\n#include <KnownFolders.h> // rt_files.c\n#include <AclAPI.h>       // rt_files.c\n#include <ShlObj_core.h>  // rt_files.c\n#include <Shlwapi.h>      // rt_files.c\n// ui:\n#include <commdlg.h>\n#include <dbghelp.h>\n#include <dwmapi.h>\n#include <imm.h>\n#include <ShellScalingApi.h>\n#include <tlhelp32.h>\n#include <VersionHelpers.h>\n#include <windowsx.h>\n#include <winnt.h>\n\n#pragma pop_macro(\"UNICODE\")\n\n#pragma warning(pop)\n\n#include <fcntl.h>\n\n#define rt_export __declspec(dllexport)\n\n// Win32 API BOOL -> errno_t translation\n\n#define rt_b2e(call) ((errno_t)(call ? 0 : GetLastError()))\n\nvoid rt_win32_close_handle(void* h);\n/* translate ix to error */\nerrno_t rt_wait_ix2e(uint32_t r);\n\n\n#endif // WIN32\n// ___________________________________ rt.c ___________________________________\n\n// #include \"ut/macos.h\" // TODO\n// #include \"ut/linux.h\" // TODO\n\n\n// ________________________________ rt_args.c _________________________________\n\nstatic void* rt_args_memory;\n\nstatic void rt_args_main(int32_t argc, const char* argv[], const char** env) {\n    rt_swear(rt_args.c == 0 && rt_args.v == null && rt_args.env == null);\n    rt_swear(rt_args_memory == null);\n    rt_args.c = argc;\n    rt_args.v = argv;\n    rt_args.env = env;\n}\n\nstatic int32_t rt_args_option_index(const char* option) {\n    for (int32_t i = 1; i < rt_args.c; i++) {\n        if (strcmp(rt_args.v[i], \"--\") == 0) { break; } // no options after '--'\n        if (strcmp(rt_args.v[i], option) == 0) { return i; }\n    }\n    return -1;\n}\n\nstatic void rt_args_remove_at(int32_t ix) {\n    // returns new argc\n    rt_assert(0 < rt_args.c);\n    rt_assert(0 < ix && ix < rt_args.c); // cannot remove rt_args.v[0]\n    for (int32_t i = ix; i < rt_args.c; i++) {\n        rt_args.v[i] = rt_args.v[i + 1];\n    }\n    rt_args.v[rt_args.c - 1] = \"\";\n    rt_args.c--;\n}\n\nstatic bool rt_args_option_bool(const char* option) {\n    int32_t ix = rt_args_option_index(option);\n    if (ix > 0) { rt_args_remove_at(ix); }\n    return ix > 0;\n}\n\nstatic bool rt_args_option_int(const char* option, int64_t *value) {\n    int32_t ix = rt_args_option_index(option);\n    if (ix > 0 && ix < rt_args.c - 1) {\n        const char* s = rt_args.v[ix + 1];\n        int32_t base = (strstr(s, \"0x\") == s || strstr(s, \"0X\") == s) ? 16 : 10;\n        const char* b = s + (base == 10 ? 0 : 2);\n        char* e = null;\n        errno = 0;\n        int64_t v = strtoll(b, &e, base);\n        if (errno == 0 && e > b && *e == 0) {\n            *value = v;\n        } else {\n            ix = -1;\n        }\n    } else {\n        ix = -1;\n    }\n    if (ix > 0) {\n        rt_args_remove_at(ix); // remove option\n        rt_args_remove_at(ix); // remove following number\n    }\n    return ix > 0;\n}\n\nstatic const char* rt_args_option_str(const char* option) {\n    int32_t ix = rt_args_option_index(option);\n    const char* s = null;\n    if (ix > 0 && ix < rt_args.c - 1) {\n        s = rt_args.v[ix + 1];\n    } else {\n        ix = -1;\n    }\n    if (ix > 0) {\n        rt_args_remove_at(ix); // remove option\n        rt_args_remove_at(ix); // remove following string\n    }\n    return ix > 0 ? s : null;\n}\n\n// Terminology: \"quote\" in the code and comments below\n// actually refers to \"fp64_t quote mark\" and used for brevity.\n\n// TODO: posix like systems\n// Looks like all shells support quote marks but\n// AFAIK MacOS bash and zsh also allow (and prefer) backslash escaped\n// space character. Unclear what other escaping shell and posix compliant\n// parser should support.\n// Lengthy discussion here:\n// https://stackoverflow.com/questions/1706551/parse-string-into-argv-argc\n\n// Microsoft specific argument parsing:\n// https://web.archive.org/web/20231115181633/http://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=msvc-170\n// Alternative: just use CommandLineToArgvW()\n\ntypedef struct { const char* s; char* d; const char* e; } rt_args_pair_t;\n\nstatic rt_args_pair_t rt_args_parse_backslashes(rt_args_pair_t p) {\n    enum { quote = '\"', backslash = '\\\\' };\n    const char* s = p.s;\n    char* d = p.d;\n    rt_swear(*s == backslash);\n    int32_t bsc = 0; // number of backslashes\n    while (*s == backslash) { s++; bsc++; }\n    if (*s == quote) {\n        while (bsc > 1 && d < p.e) { *d++ = backslash; bsc -= 2; }\n        if (bsc == 1 && d < p.e) { *d++ = *s++; }\n    } else {\n        // Backslashes are interpreted literally,\n        // unless they immediately precede a quote:\n        while (bsc > 0 && d < p.e) { *d++ = backslash; bsc--; }\n    }\n    return (rt_args_pair_t){ .s = s, .d = d, .e = p.e };\n}\n\nstatic rt_args_pair_t rt_args_parse_quoted(rt_args_pair_t p) {\n    enum { quote = '\"', backslash = '\\\\' };\n    const char* s = p.s;\n    char* d = p.d;\n    rt_swear(*s == quote);\n    s++; // opening quote (skip)\n    while (*s != 0x00) {\n        if (*s == backslash) {\n            p = rt_args_parse_backslashes((rt_args_pair_t){\n                        .s = s, .d = d, .e = p.e });\n            s = p.s; d = p.d;\n        } else if (*s == quote && s[1] == quote) {\n            // Within a quoted string, a pair of quote is\n            // interpreted as a single escaped quote.\n            if (d < p.e) { *d++ = *s++; }\n            s++; // 1 for 2 quotes\n        } else if (*s == quote) {\n            s++; // closing quote (skip)\n            break;\n        } else if (d < p.e) {\n            *d++ = *s++;\n        }\n    }\n    return (rt_args_pair_t){ .s = s, .d = d, .e = p.e };\n}\n\nstatic void rt_args_parse(const char* s) {\n    rt_swear(s[0] != 0, \"cannot parse empty string\");\n    rt_swear(rt_args.c == 0);\n    rt_swear(rt_args.v == null);\n    rt_swear(rt_args_memory == null);\n    enum { quote = '\"', backslash = '\\\\', tab = '\\t', space = 0x20 };\n    const int32_t len = (int32_t)strlen(s);\n    // Worst-case scenario (possible to optimize with dry run of parse)\n    // at least 2 characters per token in \"a b c d e\" plush null at the end:\n    const int32_t k = ((len + 2) / 2 + 1) * (int32_t)sizeof(void*) + (int32_t)sizeof(void*);\n    const int32_t n = k + (len + 2) * (int32_t)sizeof(char);\n    rt_fatal_if_error(rt_heap.allocate(null, &rt_args_memory, n, true));\n    rt_args.c = 0;\n    rt_args.v = (const char**)rt_args_memory;\n    char* d = (char*)(((char*)rt_args.v) + k);\n    char* e = d + n; // end of memory\n    // special rules for 1st argument:\n    if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n    if (*s == quote) {\n        s++;\n        while (*s != 0x00 && *s != quote && d < e) { *d++ = *s++; }\n        if (*s == quote) { // // closing quote\n            s++; // skip closing quote\n            *d++ = 0x00;\n        } else {\n            while (*s != 0x00) { s++; }\n        }\n    } else {\n        while (*s != 0x00 && *s != space && *s != tab && d < e) {\n            *d++ = *s++;\n        }\n    }\n    if (d < e) { *d++ = 0; }\n    while (d < e) {\n        while (*s == space || *s == tab) { s++; }\n        if (*s == 0) { break; }\n        if (*s == quote && s[1] == 0 && d < e) { // unbalanced single quote\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; } // spec does not say what to do\n            *d++ = *s++;\n        } else if (*s == quote) { // quoted arg\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n            rt_args_pair_t p = rt_args_parse_quoted(\n                    (rt_args_pair_t){ .s = s, .d = d, .e = e });\n            s = p.s; d = p.d;\n        } else { // non-quoted arg (that can have quoted strings inside)\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n            while (*s != 0) {\n                if (*s == backslash) {\n                    rt_args_pair_t p = rt_args_parse_backslashes(\n                            (rt_args_pair_t){ .s = s, .d = d, .e = e });\n                    s = p.s; d = p.d;\n                } else if (*s == quote) {\n                    rt_args_pair_t p = rt_args_parse_quoted(\n                            (rt_args_pair_t){ .s = s, .d = d, .e = e });\n                    s = p.s; d = p.d;\n                } else if (*s == tab || *s == space) {\n                    break;\n                } else if (d < e) {\n                    *d++ = *s++;\n                }\n            }\n        }\n        if (d < e) { *d++ = 0; }\n    }\n    if (rt_args.c < n) {\n        rt_args.v[rt_args.c] = null;\n    }\n    rt_swear(rt_args.c < n, \"not enough memory - adjust guestimates\");\n    rt_swear(d <= e, \"not enough memory - adjust guestimates\");\n}\n\nstatic const char* rt_args_basename(void) {\n    static char basename[260];\n    rt_swear(rt_args.c > 0);\n    if (basename[0] == 0) {\n        const char* s = rt_args.v[0];\n        const char* b = s;\n        while (*s != 0) {\n            if (*s == '\\\\' || *s == '/') { b = s + 1; }\n            s++;\n        }\n        int32_t n = rt_str.len(b);\n        rt_swear(n < rt_countof(basename));\n        strncpy(basename, b, rt_countof(basename) - 1);\n        char* d = basename + n - 1;\n        while (d > basename && *d != '.') { d--; }\n        if (*d == '.') { *d = 0x00; }\n    }\n    return basename;\n}\n\nstatic void rt_args_fini(void) {\n    rt_heap.deallocate(null, rt_args_memory); // can be null is parse() was not called\n    rt_args_memory = null;\n    rt_args.c = 0;\n    rt_args.v = null;\n}\n\nstatic void rt_args_WinMain(void) {\n    rt_swear(rt_args.c == 0 && rt_args.v == null && rt_args.env == null);\n    rt_swear(rt_args_memory == null);\n    const uint16_t* wcl = GetCommandLineW();\n    int32_t n = (int32_t)rt_str.len16(wcl);\n    char* cl = null;\n    rt_fatal_if_error(rt_heap.allocate(null, (void**)&cl, n * 2 + 1, false));\n    rt_str.utf16to8(cl, n * 2 + 1, wcl, -1);\n    rt_args_parse(cl);\n    rt_heap.deallocate(null, cl);\n    rt_args.env = (const char**)(void*)_environ;\n}\n\n#ifdef RT_TESTS\n\n// https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments\n// Command-line input       argv[1]     argv[2]\t    argv[3]\n// \"a b c\" d e\t            a b c       d           e\n// \"ab\\\"c\" \"\\\\\" d           ab\"c        \\           d\n// a\\\\\\b d\"e f\"g h          a\\\\\\b       de fg       h\n// a\\\\\\\"b c d               a\\\"b        c           d\n// a\\\\\\\\\"b c\" d e           a\\\\b c      d           e\n// a\"b\"\" c d                ab\" c d\n\n#ifndef __INTELLISENSE__ // confused data analysis\n\nstatic void rt_args_test_verify(const char* cl, int32_t expected, ...) {\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        rt_println(\"cl: `%s`\", cl);\n    }\n    int32_t argc = rt_args.c;\n    const char** argv = rt_args.v;\n    void* memory = rt_args_memory;\n    rt_args.c = 0;\n    rt_args.v = null;\n    rt_args_memory = null;\n    rt_args_parse(cl);\n    va_list va;\n    va_start(va, expected);\n    for (int32_t i = 0; i < expected; i++) {\n        const char* s = va_arg(va, const char*);\n//      if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//          rt_println(\"rt_args.v[%d]: `%s` expected: `%s`\", i, rt_args.v[i], s);\n//      }\n        // Warning 6385: reading data outside of array\n        const char* ai = _Pragma(\"warning(suppress:  6385)\")rt_args.v[i];\n        rt_swear(strcmp(ai, s) == 0, \"rt_args.v[%d]: `%s` expected: `%s`\",\n                 i, ai, s);\n    }\n    va_end(va);\n    rt_args.fini();\n    // restore command line arguments:\n    rt_args.c = argc;\n    rt_args.v = argv;\n    rt_args_memory = memory;\n}\n\n#endif // __INTELLISENSE__\n\nstatic void rt_args_test(void) {\n    // The first argument (rt_args.v[0]) is treated specially.\n    // It represents the program name. Because it must be a valid pathname,\n    // parts surrounded by quote (\") are allowed. The quote aren't included\n    // in the rt_args.v[0] output. The parts surrounded by quote prevent\n    // interpretation of a space or tab character as the end of the argument.\n    // The escaping rules don't apply.\n    rt_args_test_verify(\"\\\"c:\\\\foo\\\\bar\\\\snafu.exe\\\"\", 1,\n                     \"c:\\\\foo\\\\bar\\\\snafu.exe\");\n    rt_args_test_verify(\"c:\\\\foo\\\\bar\\\\snafu.exe\", 1,\n                     \"c:\\\\foo\\\\bar\\\\snafu.exe\");\n    rt_args_test_verify(\"foo.exe \\\"a b c\\\" d e\", 4,\n                     \"foo.exe\", \"a b c\", \"d\", \"e\");\n    rt_args_test_verify(\"foo.exe \\\"ab\\\\\\\"c\\\" \\\"\\\\\\\\\\\" d\", 4,\n                     \"foo.exe\", \"ab\\\"c\", \"\\\\\", \"d\");\n    rt_args_test_verify(\"foo.exe a\\\\\\\\\\\\b d\\\"e f\\\"g h\", 4,\n                     \"foo.exe\", \"a\\\\\\\\\\\\b\", \"de fg\", \"h\");\n    rt_args_test_verify(\"foo.exe a\\\\\\\\\\\\b d\\\"e f\\\"g h\", 4,\n                     \"foo.exe\", \"a\\\\\\\\\\\\b\", \"de fg\", \"h\");\n    rt_args_test_verify(\"foo.exe a\\\"b\\\"\\\" c d\", 2, // unmatched quote\n                     \"foo.exe\", \"ab\\\" c d\");\n    // unbalanced quote and backslash:\n    rt_args_test_verify(\"foo.exe \\\"\",     2, \"foo.exe\", \"\\\"\");\n    rt_args_test_verify(\"foo.exe \\\\\",     2, \"foo.exe\", \"\\\\\");\n    rt_args_test_verify(\"foo.exe \\\\\\\\\",   2, \"foo.exe\", \"\\\\\\\\\");\n    rt_args_test_verify(\"foo.exe \\\\\\\\\\\\\", 2, \"foo.exe\", \"\\\\\\\\\\\\\");\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_args_test(void) {}\n\n#endif\n\nrt_args_if rt_args = {\n    .main         = rt_args_main,\n    .WinMain      = rt_args_WinMain,\n    .option_index = rt_args_option_index,\n    .remove_at    = rt_args_remove_at,\n    .option_bool  = rt_args_option_bool,\n    .option_int   = rt_args_option_int,\n    .option_str   = rt_args_option_str,\n    .basename     = rt_args_basename,\n    .fini         = rt_args_fini,\n    .test         = rt_args_test\n};\n// _______________________________ rt_atomics.c _______________________________\n\n#include <stdatomic.h> // needs cl.exe /experimental:c11atomics command line\n\n// see: https://developercommunity.visualstudio.com/t/C11--C17-include-stdatomich-issue/10620622\n\n#pragma warning(push)\n#pragma warning(disable: 4746) // volatile access of 'int32_var' is subject to /volatile:<iso|ms> setting; consider using __iso_volatile_load/store intrinsic functions\n\n#ifndef UT_ATOMICS_HAS_STDATOMIC_H\n\nstatic int32_t rt_atomics_increment_int32(volatile int32_t* a) {\n    return InterlockedIncrement((volatile LONG*)a);\n}\n\nstatic int32_t rt_atomics_decrement_int32(volatile int32_t* a) {\n    return InterlockedDecrement((volatile LONG*)a);\n}\n\nstatic int64_t rt_atomics_increment_int64(volatile int64_t* a) {\n    return InterlockedIncrement64((__int64 volatile *)a);\n}\n\nstatic int64_t rt_atomics_decrement_int64(volatile int64_t* a) {\n    return InterlockedDecrement64((__int64 volatile *)a);\n}\n\nstatic int32_t rt_atomics_add_int32(volatile int32_t* a, int32_t v) {\n    return InterlockedAdd((LONG volatile *)a, v);\n}\n\nstatic int64_t rt_atomics_add_int64(volatile int64_t* a, int64_t v) {\n    return InterlockedAdd64((__int64 volatile *)a, v);\n}\n\nstatic int64_t rt_atomics_exchange_int64(volatile int64_t* a, int64_t v) {\n    return (int64_t)InterlockedExchange64((LONGLONG*)a, (LONGLONG)v);\n}\n\nstatic int32_t rt_atomics_exchange_int32(volatile int32_t* a, int32_t v) {\n    rt_assert(sizeof(int32_t) == sizeof(unsigned long));\n    return (int32_t)InterlockedExchange((volatile LONG*)a, (unsigned long)v);\n}\n\nstatic bool rt_atomics_compare_exchange_int64(volatile int64_t* a,\n        int64_t comparand, int64_t v) {\n    return (int64_t)InterlockedCompareExchange64((LONGLONG*)a,\n        (LONGLONG)v, (LONGLONG)comparand) == comparand;\n}\n\nstatic bool rt_atomics_compare_exchange_int32(volatile int32_t* a,\n        int32_t comparand, int32_t v) {\n    return (int64_t)InterlockedCompareExchange((LONG*)a,\n        (LONG)v, (LONG)comparand) == comparand;\n}\n\nstatic void memory_fence(void) {\n#ifdef _M_ARM64\natomic_thread_fence(memory_order_seq_cst);\n#else\n_mm_mfence();\n#endif\n}\n\n#else\n\n// stdatomic.h version:\n\n#ifndef __INTELLISENSE__ // IntelliSense chokes on _Atomic(_Type)\n// __INTELLISENSE__ Defined as 1 during an IntelliSense compiler pass\n// in the Visual Studio IDE. Otherwise, undefined. You can use this macro\n// to guard code the IntelliSense compiler doesn't understand,\n// or use it to toggle between the build and IntelliSense compiler.\n\n\n// _strong() operations are the same as _explicit(..., memory_order_seq_cst)\n// memory_order_seq_cst stands for Sequentially Consistent Ordering\n//\n// This is the strongest memory order, providing the guarantee that\n// all sequentially consistent operations appear to be executed in\n// the same order on all threads (cores)\n//\n// int_fast32_t: Fastest integer type with at least 32 bits.\n// int_least32_t: Smallest integer type with at least 32 bits.\n\nrt_static_assertion(sizeof(int32_t) == sizeof(int_fast32_t));\nrt_static_assertion(sizeof(int32_t) == sizeof(int_least32_t));\n\nstatic int32_t rt_atomics_increment_int32(volatile int32_t* a) {\n    return atomic_fetch_add((volatile atomic_int_fast32_t*)a, 1) + 1;\n}\n\nstatic int32_t rt_atomics_decrement_int32(volatile int32_t* a) {\n    return atomic_fetch_sub((volatile atomic_int_fast32_t*)a, 1) - 1;\n}\n\nstatic int64_t rt_atomics_increment_int64(volatile int64_t* a) {\n    return atomic_fetch_add((volatile atomic_int_fast64_t*)a, 1) + 1;\n}\n\nstatic int64_t rt_atomics_decrement_int64(volatile int64_t* a) {\n    return atomic_fetch_sub((volatile atomic_int_fast64_t*)a, 1) - 1;\n}\n\nstatic int32_t rt_atomics_add_int32(volatile int32_t* a, int32_t v) {\n    return atomic_fetch_add((volatile atomic_int_fast32_t*)a, v) + v;\n}\n\nstatic int64_t rt_atomics_add_int64(volatile int64_t* a, int64_t v) {\n    return atomic_fetch_add((volatile atomic_int_fast64_t*)a, v) + v;\n}\n\nstatic int64_t rt_atomics_exchange_int64(volatile int64_t* a, int64_t v) {\n    return atomic_exchange((volatile atomic_int_fast64_t*)a, v);\n}\n\nstatic int32_t rt_atomics_exchange_int32(volatile int32_t* a, int32_t v) {\n    return atomic_exchange((volatile atomic_int_fast32_t*)a, v);\n}\n\nstatic bool rt_atomics_compare_exchange_int64(volatile int64_t* a,\n    int64_t comparand, int64_t v) {\n    return atomic_compare_exchange_strong((volatile atomic_int_fast64_t*)a,\n        &comparand, v);\n}\n\n// Code here is not \"seen\" by IntelliSense but is compiled normally.\nstatic bool rt_atomics_compare_exchange_int32(volatile int32_t* a,\n    int32_t comparand, int32_t v) {\n    return atomic_compare_exchange_strong((volatile atomic_int_fast32_t*)a,\n        &comparand, v);\n}\n\nstatic void memory_fence(void) { atomic_thread_fence(memory_order_seq_cst); }\n\n#endif // __INTELLISENSE__\n\n#endif // UT_ATOMICS_HAS_STDATOMIC_H\n\nstatic int32_t rt_atomics_load_int32(volatile int32_t* a) {\n    return rt_atomics.add_int32(a, 0);\n}\n\nstatic int64_t rt_atomics_load_int64(volatile int64_t* a) {\n    return rt_atomics.add_int64(a, 0);\n}\n\nstatic void* rt_atomics_exchange_ptr(volatile void* *a, void* v) {\n    rt_static_assertion(sizeof(void*) == sizeof(uint64_t));\n    return (void*)(intptr_t)rt_atomics.exchange_int64((int64_t*)a, (int64_t)v);\n}\n\nstatic bool rt_atomics_compare_exchange_ptr(volatile void* *a, void* comparand, void* v) {\n    rt_static_assertion(sizeof(void*) == sizeof(int64_t));\n    return rt_atomics.compare_exchange_int64((int64_t*)a,\n        (int64_t)comparand, (int64_t)v);\n}\n\n#pragma push_macro(\"rt_sync_bool_compare_and_swap\")\n#pragma push_macro(\"rt_builtin_cpu_pause\")\n\n// https://en.wikipedia.org/wiki/Spinlock\n\n#define rt_sync_bool_compare_and_swap(p, old_val, new_val)          \\\n    (_InterlockedCompareExchange64(p, new_val, old_val) == old_val)\n\n// https://stackoverflow.com/questions/37063700/mm-pause-usage-in-gcc-on-intel\n#define rt_builtin_cpu_pause() do { YieldProcessor(); } while (0)\n\nstatic void spinlock_acquire(volatile int64_t* spinlock) {\n    // Very basic implementation of a spinlock. This is currently\n    // only used to guarantee thread-safety during context initialization\n    // and shutdown (which are both executed very infrequently and\n    // have minimal thread contention).\n    // Not a performance champion (because of mem_fence()) but serves\n    // the purpose. mem_fence() can be reduced to mem_sfence()... sigh\n    while (!rt_sync_bool_compare_and_swap(spinlock, 0, 1)) {\n        while (*spinlock) { rt_builtin_cpu_pause(); }\n    }\n    rt_atomics.memory_fence();\n    // not strictly necessary on strong mem model Intel/AMD but\n    // see: https://cfsamsonbooks.gitbook.io/explaining-atomics-in-rust/\n    //      Fig 2 Inconsistent C11 execution of SB and 2+2W\n    rt_assert(*spinlock == 1);\n}\n\n#pragma pop_macro(\"rt_builtin_cpu_pause\")\n#pragma pop_macro(\"rt_sync_bool_compare_and_swap\")\n\nstatic void spinlock_release(volatile int64_t* spinlock) {\n    rt_assert(*spinlock == 1);\n    *spinlock = 0;\n    // tribute to lengthy Linus discussion going since 2006:\n    rt_atomics.memory_fence();\n}\n\nstatic void rt_atomics_test(void) {\n    #ifdef RT_TESTS\n    volatile int32_t int32_var = 0;\n    volatile int64_t int64_var = 0;\n    volatile void* ptr_var = null;\n    int64_t spinlock = 0;\n    void* old_ptr = rt_atomics.exchange_ptr(&ptr_var, (void*)123);\n    rt_swear(old_ptr == null);\n    rt_swear(ptr_var == (void*)123);\n    int32_t incremented_int32 = rt_atomics.increment_int32(&int32_var);\n    rt_swear(incremented_int32 == 1);\n    rt_swear(int32_var == 1);\n    int32_t decremented_int32 = rt_atomics.decrement_int32(&int32_var);\n    rt_swear(decremented_int32 == 0);\n    rt_swear(int32_var == 0);\n    int64_t incremented_int64 = rt_atomics.increment_int64(&int64_var);\n    rt_swear(incremented_int64 == 1);\n    rt_swear(int64_var == 1);\n    int64_t decremented_int64 = rt_atomics.decrement_int64(&int64_var);\n    rt_swear(decremented_int64 == 0);\n    rt_swear(int64_var == 0);\n    int32_t added_int32 = rt_atomics.add_int32(&int32_var, 5);\n    rt_swear(added_int32 == 5);\n    rt_swear(int32_var == 5);\n    int64_t added_int64 = rt_atomics.add_int64(&int64_var, 10);\n    rt_swear(added_int64 == 10);\n    rt_swear(int64_var == 10);\n    int32_t old_int32 = rt_atomics.exchange_int32(&int32_var, 3);\n    rt_swear(old_int32 == 5);\n    rt_swear(int32_var == 3);\n    int64_t old_int64 = rt_atomics.exchange_int64(&int64_var, 6);\n    rt_swear(old_int64 == 10);\n    rt_swear(int64_var == 6);\n    bool int32_exchanged = rt_atomics.compare_exchange_int32(&int32_var, 3, 4);\n    rt_swear(int32_exchanged);\n    rt_swear(int32_var == 4);\n    bool int64_exchanged = rt_atomics.compare_exchange_int64(&int64_var, 6, 7);\n    rt_swear(int64_exchanged);\n    rt_swear(int64_var == 7);\n    ptr_var = (void*)0x123;\n    bool ptr_exchanged = rt_atomics.compare_exchange_ptr(&ptr_var,\n        (void*)0x123, (void*)0x456);\n    rt_swear(ptr_exchanged);\n    rt_swear(ptr_var == (void*)0x456);\n    rt_atomics.spinlock_acquire(&spinlock);\n    rt_swear(spinlock == 1);\n    rt_atomics.spinlock_release(&spinlock);\n    rt_swear(spinlock == 0);\n    int32_t loaded_int32 = rt_atomics.load32(&int32_var);\n    rt_swear(loaded_int32 == int32_var);\n    int64_t loaded_int64 = rt_atomics.load64(&int64_var);\n    rt_swear(loaded_int64 == int64_var);\n    rt_atomics.memory_fence();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\n#ifndef __INTELLISENSE__ // IntelliSense chokes on _Atomic(_Type)\n\nrt_static_assertion(sizeof(void*) == sizeof(int64_t));\nrt_static_assertion(sizeof(void*) == sizeof(uintptr_t));\n\nrt_atomics_if rt_atomics = {\n    .exchange_ptr    = rt_atomics_exchange_ptr,\n    .increment_int32 = rt_atomics_increment_int32,\n    .decrement_int32 = rt_atomics_decrement_int32,\n    .increment_int64 = rt_atomics_increment_int64,\n    .decrement_int64 = rt_atomics_decrement_int64,\n    .add_int32 = rt_atomics_add_int32,\n    .add_int64 = rt_atomics_add_int64,\n    .exchange_int32  = rt_atomics_exchange_int32,\n    .exchange_int64  = rt_atomics_exchange_int64,\n    .compare_exchange_int64 = rt_atomics_compare_exchange_int64,\n    .compare_exchange_int32 = rt_atomics_compare_exchange_int32,\n    .compare_exchange_ptr = rt_atomics_compare_exchange_ptr,\n    .load32 = rt_atomics_load_int32,\n    .load64 = rt_atomics_load_int64,\n    .spinlock_acquire = spinlock_acquire,\n    .spinlock_release = spinlock_release,\n    .memory_fence = memory_fence,\n    .test = rt_atomics_test\n};\n\n#endif // __INTELLISENSE__\n\n// 2024-03-20 latest windows runtime and toolchain cl.exe\n// ... VC\\Tools\\MSVC\\14.39.33519\\include\n// see:\n//     vcruntime_c11_atomic_support.h\n//     vcruntime_c11_stdatomic.h\n//     stdatomic.h\n// https://developercommunity.visualstudio.com/t/C11--C17-include--issue/10620622\n// cl.exe /std:c11 /experimental:c11atomics\n// command line option are required\n// even in C17 mode in spring of 2024\n\n#pragma warning(pop)\n\n// ______________________________ rt_backtrace.c ______________________________\n\nstatic void* rt_backtrace_process;\nstatic DWORD rt_backtrace_pid;\n\ntypedef rt_begin_packed struct symbol_info_s {\n    SYMBOL_INFO info; char name[rt_backtrace_max_symbol];\n} rt_end_packed symbol_info_t;\n\n#pragma push_macro(\"rt_backtrace_load_dll\")\n\n#define rt_backtrace_load_dll(fn) do {              \\\n    if (GetModuleHandleA(fn) == null) {      \\\n        rt_fatal_win32err(LoadLibraryA(fn)); \\\n    }                                        \\\n} while (0)\n\nstatic void rt_backtrace_init(void) {\n    if (rt_backtrace_process == null) {\n        rt_backtrace_load_dll(\"dbghelp.dll\");\n        rt_backtrace_load_dll(\"imagehlp.dll\");\n        DWORD options = SymGetOptions();\n//      options |= SYMOPT_DEBUG;\n        options |= SYMOPT_NO_PROMPTS;\n        options |= SYMOPT_LOAD_LINES;\n        options |= SYMOPT_UNDNAME;\n        options |= SYMOPT_LOAD_ANYTHING;\n        rt_swear(SymSetOptions(options));\n        rt_backtrace_pid = GetProcessId(GetCurrentProcess());\n        rt_swear(rt_backtrace_pid != 0);\n        rt_backtrace_process = OpenProcess(PROCESS_ALL_ACCESS, false,\n                                           rt_backtrace_pid);\n        rt_swear(rt_backtrace_process != null);\n        rt_swear(SymInitialize(rt_backtrace_process, null, true), \"%s\",\n                            rt_str.error(rt_core.err()));\n    }\n}\n\n#pragma pop_macro(\"rt_backtrace_load_dll\")\n\nstatic void rt_backtrace_capture(rt_backtrace_t* bt, int32_t skip) {\n    rt_backtrace_init();\n    SetLastError(0);\n    bt->frames = CaptureStackBackTrace(1 + skip, rt_countof(bt->stack),\n        bt->stack, (DWORD*)&bt->hash);\n    bt->error = rt_core.err();\n}\n\nstatic bool rt_backtrace_function(DWORD64 pc, SYMBOL_INFO* si) {\n    // find DLL exported function\n    bool found = false;\n    const DWORD64 module_base = SymGetModuleBase64(rt_backtrace_process, pc);\n    if (module_base != 0) {\n        const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT |\n                            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS;\n        HMODULE module_handle = null;\n        if (GetModuleHandleExA(flags, (const char*)pc, &module_handle)) {\n            DWORD bytes = 0;\n            IMAGE_EXPORT_DIRECTORY* dir = (IMAGE_EXPORT_DIRECTORY*)\n                    ImageDirectoryEntryToDataEx(module_handle, true,\n                            IMAGE_DIRECTORY_ENTRY_EXPORT, &bytes, null);\n            if (dir) {\n                uint8_t* m = (uint8_t*)module_handle;\n                DWORD* functions = (DWORD*)(m + dir->AddressOfFunctions);\n                DWORD* names = (DWORD*)(m + dir->AddressOfNames);\n                WORD* ordinals = (WORD*)(m + dir->AddressOfNameOrdinals);\n                DWORD64 address = 0; // closest address\n                DWORD64 min_distance = (DWORD64)-1;\n                const char* function = NULL; // closest function name\n                for (DWORD i = 0; i < dir->NumberOfNames; i++) {\n                    // function address\n                    DWORD64 fa = (DWORD64)(m + functions[ordinals[i]]);\n                    if (fa <= pc) {\n                        DWORD64 distance = pc - fa;\n                        if (distance < min_distance) {\n                            min_distance = distance;\n                            address = fa;\n                            function = (const char*)(m + names[i]);\n                        }\n                    }\n                }\n                if (function != null) {\n                    si->ModBase = (uint64_t)m;\n                    snprintf(si->Name, si->MaxNameLen - 1, \"%s\", function);\n                    si->Name[si->MaxNameLen - 1] = 0x00;\n                    si->NameLen = (DWORD)strlen(si->Name);\n                    si->Address = address;\n                    found = true;\n                }\n            }\n        }\n    }\n    return found;\n}\n\n// SimpleStackWalker::showVariablesAt() can be implemented if needed like this:\n// https://accu.org/journals/overload/29/165/orr/\n// https://github.com/rogerorr/articles/tree/main/Debugging_Optimised_Code\n// https://github.com/rogerorr/articles/blob/main/Debugging_Optimised_Code/SimpleStackWalker.cpp#L301\n\nstatic const void rt_backtrace_symbolize_inline_frame(rt_backtrace_t* bt,\n        int32_t i, DWORD64 pc, DWORD inline_context, symbol_info_t* si) {\n    si->info.Name[0] = 0;\n    si->info.NameLen = 0;\n    bt->file[i][0] = 0;\n    bt->line[i] = 0;\n    bt->symbol[i][0] = 0;\n    DWORD64 displacement = 0;\n    if (SymFromInlineContext(rt_backtrace_process, pc, inline_context,\n                            &displacement, &si->info)) {\n        rt_str_printf(bt->symbol[i], \"%s\", si->info.Name);\n    } else {\n        bt->error = rt_core.err();\n    }\n    IMAGEHLP_LINE64 li = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };\n    DWORD offset = 0;\n    if (SymGetLineFromInlineContext(rt_backtrace_process,\n                                    pc, inline_context, 0,\n                                    &offset, &li)) {\n        rt_str_printf(bt->file[i], \"%s\", li.FileName);\n        bt->line[i] = li.LineNumber;\n    }\n}\n\n// Too see kernel addresses in Stack Back Traces:\n//\n// Windows Registry Editor Version 5.00\n// [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management]\n// \"DisablePagingExecutive\"=dword:00000001\n//\n// https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc757875(v=ws.10)\n\nstatic int32_t rt_backtrace_symbolize_frame(rt_backtrace_t* bt, int32_t i) {\n    const DWORD64 pc = (DWORD64)bt->stack[i];\n    symbol_info_t si = {\n        .info = { .SizeOfStruct = sizeof(SYMBOL_INFO),\n                  .MaxNameLen = rt_countof(si.name) }\n    };\n    bt->file[i][0] = 0;\n    bt->line[i] = 0;\n    bt->symbol[i][0] = 0;\n    DWORD64 offsetFromSymbol = 0;\n    const DWORD inline_count =\n        SymAddrIncludeInlineTrace(rt_backtrace_process, pc);\n    if (inline_count > 0) {\n        DWORD ic = 0; // inline context\n        DWORD fi = 0; // frame index\n        if (SymQueryInlineTrace(rt_backtrace_process,\n                                pc, 0, pc, pc, &ic, &fi)) {\n            for (DWORD k = 0; k < inline_count; k++, ic++) {\n                rt_backtrace_symbolize_inline_frame(bt, i, pc, ic, &si);\n                i++;\n            }\n        }\n    } else {\n        if (SymFromAddr(rt_backtrace_process, pc, &offsetFromSymbol, &si.info)) {\n            rt_str_printf(bt->symbol[i], \"%s\", si.info.Name);\n            DWORD d = 0; // displacement\n            IMAGEHLP_LINE64 ln = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };\n            if (SymGetLineFromAddr64(rt_backtrace_process, pc, &d, &ln)) {\n                bt->line[i] = ln.LineNumber;\n                rt_str_printf(bt->file[i], \"%s\", ln.FileName);\n            } else {\n                bt->error = rt_core.err();\n                if (rt_backtrace_function(pc, &si.info)) {\n                    GetModuleFileNameA((HANDLE)si.info.ModBase, bt->file[i],\n                        rt_countof(bt->file[i]) - 1);\n                    bt->file[i][rt_countof(bt->file[i]) - 1] = 0;\n                    bt->line[i]    = 0;\n                } else  {\n                    bt->file[i][0] = 0x00;\n                    bt->line[i]    = 0;\n                }\n            }\n            i++;\n        } else {\n            bt->error = rt_core.err();\n            if (rt_backtrace_function(pc, &si.info)) {\n                rt_str_printf(bt->symbol[i], \"%s\", si.info.Name);\n                GetModuleFileNameA((HANDLE)si.info.ModBase, bt->file[i],\n                    rt_countof(bt->file[i]) - 1);\n                bt->file[i][rt_countof(bt->file[i]) - 1] = 0;\n                bt->error = 0;\n                i++;\n            } else {\n                // will not do i++\n            }\n        }\n    }\n    return i;\n}\n\nstatic void rt_backtrace_symbolize_backtrace(rt_backtrace_t* bt) {\n    rt_assert(!bt->symbolized);\n    bt->error = 0;\n    rt_backtrace_init();\n    // rt_backtrace_symbolize_frame() may produce zero, one or many frames\n    int32_t n = bt->frames;\n    void* stack[rt_countof(bt->stack)];\n    memcpy(stack, bt->stack, n * sizeof(stack[0]));\n    bt->frames = 0;\n    for (int32_t i = 0; i < n && bt->frames < rt_countof(bt->stack); i++) {\n        bt->stack[bt->frames] = stack[i];\n        bt->frames = rt_backtrace_symbolize_frame(bt, i);\n    }\n    bt->symbolized = true;\n}\n\nstatic void rt_backtrace_symbolize(rt_backtrace_t* bt) {\n    if (!bt->symbolized) { rt_backtrace_symbolize_backtrace(bt); }\n}\n\nstatic const char* rt_backtrace_stops[] = {\n    \"main\",\n    \"WinMain\",\n    \"BaseThreadInitThunk\",\n    \"RtlUserThreadStart\",\n    \"mainCRTStartup\",\n    \"WinMainCRTStartup\",\n    \"invoke_main\",\n    \"NdrInterfacePointerMemorySize\",\n    null\n};\n\nstatic void rt_backtrace_trace(const rt_backtrace_t* bt, const char* stop) {\n    #pragma push_macro(\"rt_backtrace_glyph_called_from\")\n    #define rt_backtrace_glyph_called_from rt_glyph_north_west_arrow_with_hook\n    rt_assert(bt->symbolized, \"need rt_backtrace.symbolize(bt)\");\n    const char** alt = stop != null && strcmp(stop, \"*\") == 0 ?\n                       rt_backtrace_stops : null;\n    for (int32_t i = 0; i < bt->frames; i++) {\n        rt_debug.println(bt->file[i], bt->line[i], bt->symbol[i],\n            rt_backtrace_glyph_called_from \"%s\",\n            i == i < bt->frames - 1 ? \"\\n\" : \"\"); // extra \\n for last line\n        if (stop != null && strcmp(bt->symbol[i], stop) == 0) { break; }\n        const char** s = alt;\n        while (s != null && *s != null && strcmp(bt->symbol[i], *s) != 0) { s++; }\n        if (s != null && *s != null)  { break; }\n    }\n    #pragma pop_macro(\"rt_backtrace_glyph_called_from\")\n}\n\n\nstatic const char* rt_backtrace_string(const rt_backtrace_t* bt,\n        char* text, int32_t count) {\n    rt_assert(bt->symbolized, \"need rt_backtrace.symbolize(bt)\");\n    char s[1024];\n    char* p = text;\n    int32_t n = count;\n    for (int32_t i = 0; i < bt->frames && n > 128; i++) {\n        int32_t line = bt->line[i];\n        const char* file = bt->file[i];\n        const char* name = bt->symbol[i];\n        if (file[0] != 0 && name[0] != 0) {\n            rt_str_printf(s, \"%s(%d): %s\\n\", file, line, name);\n        } else if (file[0] == 0 && name[0] != 0) {\n            rt_str_printf(s, \"%s\\n\", name);\n        }\n        s[rt_countof(s) - 1] = 0;\n        int32_t k = (int32_t)strlen(s);\n        if (k < n) {\n            memcpy(p, s, (size_t)k + 1);\n            p += k;\n            n -= k;\n        }\n    }\n    return text;\n}\n\ntypedef struct { char name[32]; } rt_backtrace_thread_name_t;\n\nstatic rt_backtrace_thread_name_t rt_backtrace_thread_name(HANDLE thread) {\n    rt_backtrace_thread_name_t tn;\n    tn.name[0] = 0;\n    wchar_t* thread_name = null;\n    if (SUCCEEDED(GetThreadDescription(thread, &thread_name))) {\n        rt_str.utf16to8(tn.name, rt_countof(tn.name), thread_name, -1);\n        LocalFree(thread_name);\n    }\n    return tn;\n}\n\nstatic void rt_backtrace_context(rt_thread_t thread, const void* ctx,\n        rt_backtrace_t* bt) {\n    CONTEXT* context = (CONTEXT*)ctx;\n    STACKFRAME64 stack_frame = { 0 };\n    int machine_type = IMAGE_FILE_MACHINE_UNKNOWN;\n    #if defined(_M_IX86)\n        #error \"Unsupported platform\"\n    #elif defined(_M_ARM64)\n        machine_type = IMAGE_FILE_MACHINE_ARM64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Pc, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Fp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Sp, .Mode = AddrModeFlat}\n        };\n    #elif defined(_M_X64)\n        machine_type = IMAGE_FILE_MACHINE_AMD64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Rip, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Rbp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Rsp, .Mode = AddrModeFlat}\n        };\n    #elif defined(_M_IA64)\n        int machine_type = IMAGE_FILE_MACHINE_IA64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC     = {.Offset = context->StIIP, .Mode = AddrModeFlat},\n            .AddrFrame  = {.Offset = context->IntSp, .Mode = AddrModeFlat},\n            .AddrBStore = {.Offset = context->RsBSP, .Mode = AddrModeFlat},\n            .AddrStack  = {.Offset = context->IntSp, .Mode = AddrModeFlat}\n        }\n    #elif defined(_M_ARM64)\n        machine_type = IMAGE_FILE_MACHINE_ARM64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Pc, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Fp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Sp, .Mode = AddrModeFlat}\n        };\n    #else\n        #error \"Unsupported platform\"\n    #endif\n    rt_backtrace_init();\n    while (StackWalk64(machine_type, rt_backtrace_process,\n            (HANDLE)thread, &stack_frame, context, null,\n            SymFunctionTableAccess64, SymGetModuleBase64, null)) {\n        DWORD64 pc = stack_frame.AddrPC.Offset;\n        if (pc == 0) { break; }\n        if (bt->frames < rt_countof(bt->stack)) {\n            bt->stack[bt->frames] = (void*)pc;\n            bt->frames = rt_backtrace_symbolize_frame(bt, bt->frames);\n        }\n    }\n    bt->symbolized = true;\n}\n\nstatic void rt_backtrace_thread(HANDLE thread, rt_backtrace_t* bt) {\n    bt->frames = 0;\n    // cannot suspend callers thread\n    rt_swear(rt_thread.id_of(thread) != rt_thread.id());\n    if (SuspendThread(thread) != (DWORD)-1) {\n        CONTEXT context = { .ContextFlags = CONTEXT_FULL };\n        GetThreadContext(thread, &context);\n        rt_backtrace.context(thread, &context, bt);\n        if (ResumeThread(thread) == (DWORD)-1) {\n            rt_println(\"ResumeThread() failed %s\", rt_str.error(rt_core.err()));\n            ExitProcess(0xBD);\n        }\n    }\n}\n\nstatic void rt_backtrace_trace_self(const char* stop) {\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.capture(&bt, 2);\n    rt_backtrace.symbolize(&bt);\n    rt_backtrace.trace(&bt, stop);\n}\n\nstatic void rt_backtrace_trace_all_but_self(void) {\n    rt_backtrace_init();\n    rt_assert(rt_backtrace_process != null && rt_backtrace_pid != 0);\n    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);\n    if (snapshot == INVALID_HANDLE_VALUE) {\n        rt_println(\"CreateToolhelp32Snapshot failed %s\",\n                rt_str.error(rt_core.err()));\n    } else {\n        THREADENTRY32 te = { .dwSize = sizeof(THREADENTRY32) };\n        if (!Thread32First(snapshot, &te)) {\n            rt_println(\"Thread32First failed %s\", rt_str.error(rt_core.err()));\n        } else {\n            do {\n                if (te.th32OwnerProcessID == rt_backtrace_pid) {\n                    static const DWORD flags = THREAD_ALL_ACCESS |\n                       THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT;\n                    uint32_t tid = te.th32ThreadID;\n                    if (tid != (uint32_t)rt_thread.id()) {\n                        HANDLE thread = OpenThread(flags, false, tid);\n                        if (thread != null) {\n                            rt_backtrace_t bt = {0};\n                            rt_backtrace_thread(thread, &bt);\n                            rt_backtrace_thread_name_t tn = rt_backtrace_thread_name(thread);\n                            rt_debug.println(\">Thread\", tid, tn.name,\n                                \"id 0x%08X (%d)\", tid, tid);\n                            if (bt.frames > 0) {\n                                rt_backtrace.trace(&bt, \"*\");\n                            }\n                            rt_debug.println(\"<Thread\", tid, tn.name, \"\");\n                            rt_win32_close_handle(thread);\n                        }\n                    }\n                }\n            } while (Thread32Next(snapshot, &te));\n        }\n        rt_win32_close_handle(snapshot);\n    }\n}\n\n#ifdef RT_TESTS\n\nstatic bool (*rt_backtrace_debug_tee)(const char* s, int32_t count);\n\nstatic char  rt_backtrace_test_output[16 * 1024];\nstatic char* rt_backtrace_test_output_p;\n\nstatic bool rt_backtrace_tee(const char* s, int32_t count) {\n    if (count > 0 && s[count - 1] == 0) { // zero terminated\n        int32_t k = (int32_t)(uintptr_t)(\n            rt_backtrace_test_output_p - rt_backtrace_test_output);\n        int32_t space = rt_countof(rt_backtrace_test_output) - k;\n        if (count < space) {\n            memcpy(rt_backtrace_test_output_p, s, count);\n            rt_backtrace_test_output_p += count - 1; // w/o 0x00\n        }\n    } else {\n        rt_debug.breakpoint(); // incorrect output() cannot append\n    }\n    return true; // intercepted, do not do OutputDebugString()\n}\n\nstatic void rt_backtrace_test_thread(void* e) {\n    rt_event.wait(*(rt_event_t*)e);\n}\n\nstatic void rt_backtrace_test(void) {\n    rt_backtrace_debug_tee = rt_debug.tee;\n    rt_backtrace_test_output_p = rt_backtrace_test_output;\n    rt_backtrace_test_output[0] = 0x00;\n    rt_debug.tee = rt_backtrace_tee;\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.capture(&bt, 0);\n    // rt_backtrace_test <- rt_core_test <- run <- main\n    rt_swear(bt.frames >= 3);\n    rt_backtrace.symbolize(&bt);\n    rt_backtrace.trace(&bt, null);\n    rt_backtrace.trace(&bt, \"main\");\n    rt_backtrace.trace(&bt, null);\n    rt_backtrace.trace(&bt, \"main\");\n    rt_event_t e = rt_event.create();\n    rt_thread_t thread = rt_thread.start(rt_backtrace_test_thread, &e);\n    rt_backtrace.trace_all_but_self();\n    rt_event.set(e);\n    rt_thread.join(thread, -1.0);\n    rt_event.dispose(e);\n    rt_debug.tee = rt_backtrace_debug_tee;\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        rt_debug.output(rt_backtrace_test_output,\n            (int32_t)strlen(rt_backtrace_test_output) + 1);\n    }\n    rt_swear(strstr(rt_backtrace_test_output, \"rt_backtrace_test\") != null,\n          \"%s\", rt_backtrace_test_output);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_backtrace_test(void) { }\n\n#endif\n\nrt_backtrace_if rt_backtrace = {\n    .capture            = rt_backtrace_capture,\n    .context            = rt_backtrace_context,\n    .symbolize          = rt_backtrace_symbolize,\n    .trace              = rt_backtrace_trace,\n    .trace_self         = rt_backtrace_trace_self,\n    .trace_all_but_self = rt_backtrace_trace_all_but_self,\n    .string             = rt_backtrace_string,\n    .test               = rt_backtrace_test\n};\n\n// ______________________________ rt_clipboard.c ______________________________\n\nstatic errno_t rt_clipboard_put_text(const char* utf8) {\n    int32_t chars = rt_str.utf16_chars(utf8, -1);\n    int32_t bytes = (chars + 1) * 2;\n    uint16_t* utf16 = null;\n    errno_t r = rt_heap.alloc((void**)&utf16, (size_t)bytes);\n    if (utf16 != null) {\n        rt_str.utf8to16(utf16, bytes, utf8, -1);\n        rt_assert(utf16[chars - 1] == 0);\n        const int32_t n = (int32_t)rt_str.len16(utf16) + 1;\n        r = OpenClipboard(GetDesktopWindow()) ? 0 : rt_core.err();\n        if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n        if (r == 0) {\n            r = EmptyClipboard() ? 0 : rt_core.err();\n            if (r != 0) { rt_println(\"EmptyClipboard() failed %s\", rt_strerr(r)); }\n        }\n        void* global = null;\n        if (r == 0) {\n            global = GlobalAlloc(GMEM_MOVEABLE, (size_t)n * 2);\n            r = global != null ? 0 : rt_core.err();\n            if (r != 0) { rt_println(\"GlobalAlloc() failed %s\", rt_strerr(r)); }\n        }\n        if (r == 0) {\n            char* d = (char*)GlobalLock(global);\n            rt_not_null(d);\n            memcpy(d, utf16, (size_t)n * 2);\n            r = rt_b2e(SetClipboardData(CF_UNICODETEXT, global));\n            GlobalUnlock(global);\n            if (r != 0) {\n                rt_println(\"SetClipboardData() failed %s\", rt_strerr(r));\n                GlobalFree(global);\n            } else {\n                // do not free global memory. It's owned by system clipboard now\n            }\n        }\n        if (r == 0) {\n            r = rt_b2e(CloseClipboard());\n            if (r != 0) {\n                rt_println(\"CloseClipboard() failed %s\", rt_strerr(r));\n            }\n        }\n        rt_heap.free(utf16);\n    }\n    return r;\n}\n\nstatic errno_t rt_clipboard_get_text(char* utf8, int32_t* bytes) {\n    rt_not_null(bytes);\n    errno_t r = rt_b2e(OpenClipboard(GetDesktopWindow()));\n    if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n    if (r == 0) {\n        HANDLE global = GetClipboardData(CF_UNICODETEXT);\n        if (global == null) {\n            r = rt_core.err();\n        } else {\n            uint16_t* utf16 = (uint16_t*)GlobalLock(global);\n            if (utf16 != null) {\n                int32_t utf8_bytes = rt_str.utf8_bytes(utf16, -1);\n                if (utf8 != null) {\n                    char* decoded = (char*)malloc((size_t)utf8_bytes);\n                    if (decoded == null) {\n                        r = ERROR_OUTOFMEMORY;\n                    } else {\n                        rt_str.utf16to8(decoded, utf8_bytes, utf16, -1);\n                        int32_t n = rt_min(*bytes, utf8_bytes);\n                        memcpy(utf8, decoded, (size_t)n);\n                        free(decoded);\n                        if (n < utf8_bytes) {\n                            r = ERROR_INSUFFICIENT_BUFFER;\n                        }\n                    }\n                }\n                *bytes = utf8_bytes;\n                GlobalUnlock(global);\n            }\n        }\n        r = rt_b2e(CloseClipboard());\n    }\n    return r;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_clipboard_test(void) {\n    rt_fatal_if_error(rt_clipboard.put_text(\"Hello Clipboard\"));\n    char text[256];\n    int32_t bytes = rt_countof(text);\n    rt_fatal_if_error(rt_clipboard.get_text(text, &bytes));\n    rt_swear(strcmp(text, \"Hello Clipboard\") == 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_clipboard_test(void) {\n}\n\n#endif\n\nrt_clipboard_if rt_clipboard = {\n    .put_text   = rt_clipboard_put_text,\n    .get_text   = rt_clipboard_get_text,\n    .put_image  = null, // implemented in ui.app\n    .test       = rt_clipboard_test\n};\n\n// ________________________________ rt_clock.c ________________________________\n\nenum {\n    rt_clock_nsec_in_usec = 1000, // nano in micro\n    rt_clock_nsec_in_msec = rt_clock_nsec_in_usec * 1000, // nano in milli\n    rt_clock_nsec_in_sec  = rt_clock_nsec_in_msec * 1000,\n    rt_clock_usec_in_msec = 1000, // micro in mill\n    rt_clock_msec_in_sec  = 1000, // milli in sec\n    rt_clock_usec_in_sec  = rt_clock_usec_in_msec * rt_clock_msec_in_sec // micro in sec\n};\n\nstatic uint64_t rt_clock_microseconds_since_epoch(void) { // NOT monotonic\n    FILETIME ft; // time in 100ns interval (tenth of microsecond)\n    // since 12:00 A.M. January 1, 1601 Coordinated Universal Time (UTC)\n    GetSystemTimePreciseAsFileTime(&ft);\n    uint64_t microseconds =\n        (((uint64_t)ft.dwHighDateTime) << 32 | ft.dwLowDateTime) / 10;\n    rt_assert(microseconds > 0);\n    return microseconds;\n}\n\nstatic uint64_t rt_clock_localtime(void) {\n    TIME_ZONE_INFORMATION tzi; // UTC = local time + bias\n    GetTimeZoneInformation(&tzi);\n    uint64_t bias = (uint64_t)tzi.Bias * 60LL * 1000 * 1000; // in microseconds\n    return rt_clock_microseconds_since_epoch() - bias;\n}\n\nstatic void rt_clock_utc(uint64_t microseconds,\n        int32_t* year, int32_t* month, int32_t* day,\n        int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms, int32_t* mc) {\n    uint64_t time_in_100ns = microseconds * 10;\n    FILETIME mst = { (DWORD)(time_in_100ns & 0xFFFFFFFF),\n                     (DWORD)(time_in_100ns >> 32) };\n    SYSTEMTIME utc;\n    FileTimeToSystemTime(&mst, &utc);\n    *year = utc.wYear;\n    *month = utc.wMonth;\n    *day = utc.wDay;\n    *hh = utc.wHour;\n    *mm = utc.wMinute;\n    *ss = utc.wSecond;\n    *ms = utc.wMilliseconds;\n    *mc = microseconds % 1000;\n}\n\nstatic void rt_clock_local(uint64_t microseconds,\n        int32_t* year, int32_t* month, int32_t* day,\n        int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms, int32_t* mc) {\n    uint64_t time_in_100ns = microseconds * 10;\n    FILETIME mst = { (DWORD)(time_in_100ns & 0xFFFFFFFF), (DWORD)(time_in_100ns >> 32) };\n    SYSTEMTIME utc;\n    FileTimeToSystemTime(&mst, &utc);\n    DYNAMIC_TIME_ZONE_INFORMATION tzi;\n    GetDynamicTimeZoneInformation(&tzi);\n    SYSTEMTIME lt = {0};\n    SystemTimeToTzSpecificLocalTimeEx(&tzi, &utc, &lt);\n    *year = lt.wYear;\n    *month = lt.wMonth;\n    *day = lt.wDay;\n    *hh = lt.wHour;\n    *mm = lt.wMinute;\n    *ss = lt.wSecond;\n    *ms = lt.wMilliseconds;\n    *mc = microseconds % 1000;\n}\n\nstatic fp64_t rt_clock_seconds(void) { // since_boot\n    LARGE_INTEGER qpc;\n    QueryPerformanceCounter(&qpc);\n    static fp64_t one_over_freq;\n    if (one_over_freq == 0) {\n        LARGE_INTEGER frequency;\n        QueryPerformanceFrequency(&frequency);\n        one_over_freq = 1.0 / (fp64_t)frequency.QuadPart;\n    }\n    return (fp64_t)qpc.QuadPart * one_over_freq;\n}\n\n// Max duration in nanoseconds=2^64 - 1 nanoseconds\n//                          2^64 - 1 ns        1 sec          1 min\n// Max Duration in Hours =  ----------- x  ------------ x -------------\n//                          10^9 ns / s    60 sec / min   60 min / hour\n//\n//                              1 hour\n// Max Duration in Days =  ---------------\n//                          24 hours / day\n//\n// it would take approximately 213,503 days (or about 584.5 years)\n// for rt_clock.nanoseconds() to overflow\n//\n// for divider = rt_num.gcd32(nsec_in_sec, freq) below and 10MHz timer\n// the actual duration is shorter because of (mul == 100)\n//    (uint64_t)qpc.QuadPart * mul\n// 64 bit overflow and is about 5.8 years.\n//\n// In a long running code like services is advisable to use\n// rt_clock.nanoseconds() to measure only deltas and pay close attention\n// to the wrap around despite of 5 years monotony\n\nstatic uint64_t rt_clock_nanoseconds(void) {\n    LARGE_INTEGER qpc;\n    QueryPerformanceCounter(&qpc);\n    static uint32_t freq;\n    static uint32_t mul = rt_clock_nsec_in_sec;\n    if (freq == 0) {\n        LARGE_INTEGER frequency;\n        QueryPerformanceFrequency(&frequency);\n        rt_assert(frequency.HighPart == 0);\n        // even 1GHz frequency should fit into 32 bit unsigned\n        rt_assert(frequency.HighPart == 0, \"%08lX%%08lX\",\n               frequency.HighPart, frequency.LowPart);\n        // known values: 10,000,000 and 3,000,000 10MHz, 3MHz\n        rt_assert(frequency.LowPart % (1000 * 1000) == 0);\n        // if we start getting weird frequencies not\n        // multiples of MHz rt_num.gcd() approach may need\n        // to be revised in favor of rt_num.muldiv64x64()\n        freq = frequency.LowPart;\n        rt_assert(freq != 0 && freq < (uint32_t)rt_clock.nsec_in_sec);\n        // to avoid rt_num.muldiv128:\n        uint32_t divider = rt_num.gcd32((uint32_t)rt_clock.nsec_in_sec, freq);\n        freq /= divider;\n        mul  /= divider;\n    }\n    uint64_t ns_mul_freq = (uint64_t)qpc.QuadPart * mul;\n    return freq == 1 ? ns_mul_freq : ns_mul_freq / freq;\n}\n\n// Difference between 1601 and 1970 in microseconds:\n\nstatic const uint64_t rt_clock_epoch_diff_usec = 11644473600000000ULL;\n\nstatic uint64_t rt_clock_unix_microseconds(void) {\n    return rt_clock.microseconds() - rt_clock_epoch_diff_usec;\n}\n\nstatic uint64_t rt_clock_unix_seconds(void) {\n    return rt_clock.unix_microseconds() / (uint64_t)rt_clock.usec_in_sec;\n}\n\nstatic void rt_clock_test(void) {\n    #ifdef RT_TESTS\n    // TODO: implement more tests\n    uint64_t t0 = rt_clock.nanoseconds();\n    uint64_t t1 = rt_clock.nanoseconds();\n    int32_t count = 0;\n    while (t0 == t1 && count < 1024) {\n        t1 = rt_clock.nanoseconds();\n        count++;\n    }\n    rt_swear(t0 != t1, \"count: %d t0: %lld t1: %lld\", count, t0, t1);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_clock_if rt_clock = {\n    .nsec_in_usec      = rt_clock_nsec_in_usec,\n    .nsec_in_msec      = rt_clock_nsec_in_msec,\n    .nsec_in_sec       = rt_clock_nsec_in_sec,\n    .usec_in_msec      = rt_clock_usec_in_msec,\n    .msec_in_sec       = rt_clock_msec_in_sec,\n    .usec_in_sec       = rt_clock_usec_in_sec,\n    .seconds           = rt_clock_seconds,\n    .nanoseconds       = rt_clock_nanoseconds,\n    .unix_microseconds = rt_clock_unix_microseconds,\n    .unix_seconds      = rt_clock_unix_seconds,\n    .microseconds      = rt_clock_microseconds_since_epoch,\n    .localtime         = rt_clock_localtime,\n    .utc               = rt_clock_utc,\n    .local             = rt_clock_local,\n    .test              = rt_clock_test\n};\n\n// _______________________________ rt_config.c ________________________________\n\n// On Unix the implementation should keep KV pairs in\n// key-named files inside .name/ folder\n\nstatic const char* rt_config_apps = \"Software\\\\ui\\\\apps\";\n\nstatic const DWORD rt_config_access =\n    KEY_READ|KEY_WRITE|KEY_SET_VALUE|KEY_QUERY_VALUE|\n    KEY_ENUMERATE_SUB_KEYS|DELETE;\n\nstatic errno_t rt_config_get_reg_key(const char* name, HKEY *key) {\n    char path[256] = {0};\n    rt_str_printf(path, \"%s\\\\%s\", rt_config_apps, name);\n    errno_t r = RegOpenKeyExA(HKEY_CURRENT_USER, path, 0, rt_config_access, key);\n    if (r != 0) {\n        const DWORD option = REG_OPTION_NON_VOLATILE;\n        r = RegCreateKeyExA(HKEY_CURRENT_USER, path, 0, null, option,\n                            rt_config_access, null, key, null);\n    }\n    return r;\n}\n\nstatic errno_t rt_config_save(const char* name,\n        const char* key, const void* data, int32_t bytes) {\n    errno_t r = 0;\n    HKEY k = null;\n    r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        r = RegSetValueExA(k, key, 0, REG_BINARY,\n            (const uint8_t*)data, (DWORD)bytes);\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic errno_t rt_config_remove(const char* name, const char* key) {\n    errno_t r = 0;\n    HKEY k = null;\n    r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        r = RegDeleteValueA(k, key);\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic errno_t rt_config_clean(const char* name) {\n    errno_t r = 0;\n    HKEY k = null;\n    if (RegOpenKeyExA(HKEY_CURRENT_USER, rt_config_apps,\n                                      0, rt_config_access, &k) == 0) {\n       r = RegDeleteTreeA(k, name);\n       rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic int32_t rt_config_size(const char* name, const char* key) {\n    int32_t bytes = -1;\n    HKEY k = null;\n    errno_t r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        DWORD type = REG_BINARY;\n        DWORD cb = 0;\n        r = RegQueryValueExA(k, key, null, &type, null, &cb);\n        if (r == ERROR_FILE_NOT_FOUND) {\n            bytes = 0; // do not report data_size() often used this way\n        } else if (r != 0) {\n            rt_println(\"%s.RegQueryValueExA(\\\"%s\\\") failed %s\",\n                name, key, rt_strerr(r));\n            bytes = 0; // on any error behave as empty data\n        } else {\n            bytes = (int32_t)cb;\n        }\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return bytes;\n}\n\nstatic int32_t rt_config_load(const char* name,\n        const char* key, void* data, int32_t bytes) {\n    int32_t read = -1;\n    HKEY k = null;\n    errno_t r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        DWORD type = REG_BINARY;\n        DWORD cb = (DWORD)bytes;\n        r = RegQueryValueExA(k, key, null, &type, (uint8_t*)data, &cb);\n        if (r == ERROR_MORE_DATA) {\n            // returns -1 ui_app.data_size() should be used\n        } else if (r != 0) {\n            if (r != ERROR_FILE_NOT_FOUND) {\n                rt_println(\"%s.RegQueryValueExA(\\\"%s\\\") failed %s\",\n                    name, key, rt_strerr(r));\n            }\n            read = 0; // on any error behave as empty data\n        } else {\n            read = (int32_t)cb;\n        }\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return read;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_config_test(void) {\n    const char* name = strrchr(rt_args.v[0], '\\\\');\n    if (name == null) { name = strrchr(rt_args.v[0], '/'); }\n    name = name != null ? name + 1 : rt_args.v[0];\n    rt_swear(name != null);\n    const char* key = \"test\";\n    const char data[] = \"data\";\n    int32_t bytes = sizeof(data);\n    rt_swear(rt_config.save(name, key, data, bytes) == 0);\n    char read[256];\n    rt_swear(rt_config.load(name, key, read, bytes) == bytes);\n    int32_t size = rt_config.size(name, key);\n    rt_swear(size == bytes);\n    rt_swear(rt_config.remove(name, key) == 0);\n    rt_swear(rt_config.clean(name) == 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_config_test(void) { }\n\n#endif\n\nrt_config_if rt_config = {\n    .save   = rt_config_save,\n    .size   = rt_config_size,\n    .load   = rt_config_load,\n    .remove = rt_config_remove,\n    .clean  = rt_config_clean,\n    .test   = rt_config_test\n};\n\n// ________________________________ rt_core.c _________________________________\n\n// abort does NOT call atexit() functions and\n// does NOT flush rt_streams. Also Win32 runtime\n// abort() attempt to show Abort/Retry/Ignore\n// MessageBox - thus ExitProcess()\n\nstatic void rt_core_abort(void) { ExitProcess(ERROR_FATAL_APP_EXIT); }\n\nstatic void rt_core_exit(int32_t exit_code) { exit(exit_code); }\n\n// TODO: consider r = HRESULT_FROM_WIN32() and r = HRESULT_CODE(hr);\n// this separates posix error codes from win32 error codes\n\n\nstatic errno_t rt_core_err(void) { return (errno_t)GetLastError(); }\n\nstatic void rt_core_seterr(errno_t err) { SetLastError((DWORD)err); }\n\nrt_static_init(runtime) {\n    SetErrorMode(\n        // The system does not display the critical-error-handler message box.\n        // Instead, the system sends the error to the calling process:\n        SEM_FAILCRITICALERRORS|\n        // The system automatically fixes memory alignment faults and\n        // makes them invisible to the application.\n        SEM_NOALIGNMENTFAULTEXCEPT|\n        // The system does not display the Windows Error Reporting dialog.\n        SEM_NOGPFAULTERRORBOX|\n        // The OpenFile function does not display a message box when it fails\n        // to find a file. Instead, the error is returned to the caller.\n        // This error mode overrides the OF_PROMPT flag.\n        SEM_NOOPENFILEERRORBOX);\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_core_test(void) { // in alphabetical order\n    rt_args.test();\n    rt_atomics.test();\n    rt_backtrace.test();\n    rt_clipboard.test();\n    rt_clock.test();\n    rt_config.test();\n    rt_debug.test();\n    rt_event.test();\n    rt_files.test();\n    rt_generics.test();\n    rt_heap.test();\n    rt_loader.test();\n    rt_mem.test();\n    rt_mutex.test();\n    rt_num.test();\n    rt_processes.test();\n    rt_static_init_test();\n    rt_str.test();\n    rt_streams.test();\n    rt_thread.test();\n    rt_vigil.test();\n    rt_worker.test();\n}\n\n#else\n\nstatic void rt_core_test(void) { }\n\n#endif\n\nrt_core_if rt_core = {\n    .err     = rt_core_err,\n    .set_err = rt_core_seterr,\n    .abort   = rt_core_abort,\n    .exit    = rt_core_exit,\n    .test    = rt_core_test,\n    .error   = {                                              // posix\n        .access_denied          = ERROR_ACCESS_DENIED,        // EACCES\n        .bad_file               = ERROR_BAD_FILE_TYPE,        // EBADF\n        .broken_pipe            = ERROR_BROKEN_PIPE,          // EPIPE\n        .device_not_ready       = ERROR_NOT_READY,            // ENXIO\n        .directory_not_empty    = ERROR_DIR_NOT_EMPTY,        // ENOTEMPTY\n        .disk_full              = ERROR_DISK_FULL,            // ENOSPC\n        .file_exists            = ERROR_FILE_EXISTS,          // EEXIST\n        .file_not_found         = ERROR_FILE_NOT_FOUND,       // ENOENT\n        .insufficient_buffer    = ERROR_INSUFFICIENT_BUFFER,  // E2BIG\n        .interrupted            = ERROR_OPERATION_ABORTED,    // EINTR\n        .invalid_data           = ERROR_INVALID_DATA,         // EINVAL\n        .invalid_handle         = ERROR_INVALID_HANDLE,       // EBADF\n        .invalid_parameter      = ERROR_INVALID_PARAMETER,    // EINVAL\n        .io_error               = ERROR_IO_DEVICE,            // EIO\n        .more_data              = ERROR_MORE_DATA,            // ENOBUFS\n        .name_too_long          = ERROR_FILENAME_EXCED_RANGE, // ENAMETOOLONG\n        .no_child_process       = ERROR_NO_PROC_SLOTS,        // ECHILD\n        .not_a_directory        = ERROR_DIRECTORY,            // ENOTDIR\n        .not_empty              = ERROR_DIR_NOT_EMPTY,        // ENOTEMPTY\n        .out_of_memory          = ERROR_OUTOFMEMORY,          // ENOMEM\n        .path_not_found         = ERROR_PATH_NOT_FOUND,       // ENOENT\n        .pipe_not_connected     = ERROR_PIPE_NOT_CONNECTED,   // EPIPE\n        .read_only_file         = ERROR_WRITE_PROTECT,        // EROFS\n        .resource_deadlock      = ERROR_LOCK_VIOLATION,       // EDEADLK\n        .too_many_open_files    = ERROR_TOO_MANY_OPEN_FILES,  // EMFILE\n    }\n};\n\n#pragma comment(lib, \"advapi32\")\n#pragma comment(lib, \"ntdll\")\n#pragma comment(lib, \"psapi\")\n#pragma comment(lib, \"shell32\")\n#pragma comment(lib, \"shlwapi\")\n#pragma comment(lib, \"kernel32\")\n#pragma comment(lib, \"user32\") // clipboard\n#pragma comment(lib, \"imm32\")  // Internationalization input method\n#pragma comment(lib, \"ole32\")  // rt_files.known_folder CoMemFree\n#pragma comment(lib, \"dbghelp\")\n#pragma comment(lib, \"imagehlp\")\n\n\n\n// ________________________________ rt_debug.c ________________________________\n\nstatic const char* rt_debug_abbreviate(const char* file) {\n    const char* fn = strrchr(file, '\\\\');\n    if (fn == null) { fn = strrchr(file, '/'); }\n    return fn != null ? fn + 1 : file;\n}\n\n#ifdef WINDOWS\n\nstatic int32_t rt_debug_max_file_line;\nstatic int32_t rt_debug_max_function;\n\nstatic void rt_debug_output(const char* s, int32_t count) {\n    bool intercepted = false;\n    if (rt_debug.tee != null) { intercepted = rt_debug.tee(s, count); }\n    if (!intercepted) {\n        // For link.exe /Subsystem:Windows code stdout/stderr are often closed\n        if (stderr != null && fileno(stderr) >= 0) {\n            fprintf(stderr, \"%s\", s);\n        }\n        // SetConsoleCP(CP_UTF8) is not guaranteed to be called\n        uint16_t* wide = rt_stackalloc((count + 1) * sizeof(uint16_t));\n        rt_str.utf8to16(wide, count, s, -1);\n        OutputDebugStringW(wide);\n    }\n}\n\nstatic void rt_debug_println_va(const char* file, int32_t line, const char* func,\n        const char* format, va_list va) {\n    if (func == null) { func = \"\"; }\n    char file_line[1024];\n    if (line == 0 && file == null || file[0] == 0x00) {\n        file_line[0] = 0x00;\n    } else {\n        if (file == null) { file = \"\"; } // backtrace can have null files\n        // full path is useful in MSVC debugger output pane (clickable)\n        // for all other scenarios short filename without path is preferable:\n        const char* name = IsDebuggerPresent() ? file : rt_files.basename(file);\n        snprintf(file_line, rt_countof(file_line) - 1, \"%s(%d):\", name, line);\n    }\n    file_line[rt_countof(file_line) - 1] = 0x00; // always zero terminated'\n    rt_debug_max_file_line = rt_max(rt_debug_max_file_line,\n                                    (int32_t)strlen(file_line));\n    rt_debug_max_function  = rt_max(rt_debug_max_function,\n                                    (int32_t)strlen(func));\n    char prefix[2 * 1024];\n    // snprintf() does not guarantee zero termination on truncation\n    snprintf(prefix, rt_countof(prefix) - 1, \"%-*s %-*s\",\n            rt_debug_max_file_line, file_line,\n            rt_debug_max_function,  func);\n    prefix[rt_countof(prefix) - 1] = 0; // zero terminated\n    char text[2 * 1024];\n    if (format != null && format[0] != 0) {\n        #if defined(__GNUC__) || defined(__clang__)\n        #pragma GCC diagnostic push\n        #pragma GCC diagnostic ignored \"-Wformat-nonliteral\"\n        #endif\n        vsnprintf(text, rt_countof(text) - 1, format, va);\n        text[rt_countof(text) - 1] = 0;\n        #if defined(__GNUC__) || defined(__clang__)\n        #pragma GCC diagnostic pop\n        #endif\n    } else {\n        text[0] = 0;\n    }\n    char output[4 * 1024];\n    snprintf(output, rt_countof(output) - 1, \"%s %s\", prefix, text);\n    output[rt_countof(output) - 2] = 0;\n    // strip trailing \\n which can be remnant of fprintf(\"...\\n\")\n    int32_t n = (int32_t)strlen(output);\n    while (n > 0 && (output[n - 1] == '\\n' || output[n - 1] == '\\r')) {\n        output[n - 1] = 0;\n        n--;\n    }\n    rt_assert(n + 1 < rt_countof(output));\n    // Win32 OutputDebugString() needs \\n\n    output[n + 0] = '\\n';\n    output[n + 1] = 0;\n    rt_debug.output(output, n + 2); // including 0x00\n}\n\n#else // posix version:\n\nstatic void rt_debug_vprintf(const char* file, int32_t line, const char* func,\n        const char* format, va_list va) {\n    fprintf(stderr, \"%s(%d): %s \", file, line, func);\n    vfprintf(stderr, format, va);\n    fprintf(stderr, \"\\n\");\n}\n\n#endif\n\nstatic void rt_debug_perrno(const char* file, int32_t line,\n    const char* func, int32_t err_no, const char* format, ...) {\n    if (err_no != 0) {\n        if (format != null && format[0] != 0) {\n            va_list va;\n            va_start(va, format);\n            rt_debug.println_va(file, line, func, format, va);\n            va_end(va);\n        }\n        rt_debug.println(file, line, func, \"errno: %d %s\", err_no, strerror(err_no));\n    }\n}\n\nstatic void rt_debug_perror(const char* file, int32_t line,\n    const char* func, int32_t error, const char* format, ...) {\n    if (error != 0) {\n        if (format != null && format[0] != 0) {\n            va_list va;\n            va_start(va, format);\n            rt_debug.println_va(file, line, func, format, va);\n            va_end(va);\n        }\n        rt_debug.println(file, line, func, \"error: %s\", rt_strerr(error));\n    }\n}\n\nstatic void rt_debug_println(const char* file, int32_t line, const char* func,\n        const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_debug.println_va(file, line, func, format, va);\n    va_end(va);\n}\n\nstatic bool rt_debug_is_debugger_present(void) { return IsDebuggerPresent(); }\n\nstatic void rt_debug_breakpoint(void) {\n    if (rt_debug.is_debugger_present()) { DebugBreak(); }\n}\n\nstatic errno_t rt_debug_raise(uint32_t exception) {\n    rt_core.set_err(0);\n    RaiseException(exception, EXCEPTION_NONCONTINUABLE, 0, null);\n    return rt_core.err();\n}\n\nstatic int32_t rt_debug_verbosity_from_string(const char* s) {\n    char* n = null;\n    long v = strtol(s, &n, 10);\n    if (stricmp(s, \"quiet\") == 0) {\n        return rt_debug.verbosity.quiet;\n    } else if (stricmp(s, \"info\") == 0) {\n        return rt_debug.verbosity.info;\n    } else if (stricmp(s, \"verbose\") == 0) {\n        return rt_debug.verbosity.verbose;\n    } else if (stricmp(s, \"debug\") == 0) {\n        return rt_debug.verbosity.debug;\n    } else if (stricmp(s, \"trace\") == 0) {\n        return rt_debug.verbosity.trace;\n    } else if (n > s && rt_debug.verbosity.quiet <= v &&\n               v <= rt_debug.verbosity.trace) {\n        return v;\n    } else {\n        rt_fatal(\"invalid verbosity: %s\", s);\n        return rt_debug.verbosity.quiet;\n    }\n}\n\nstatic void rt_debug_test(void) {\n    #ifdef RT_TESTS\n    // not clear what can be tested here\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\n#ifndef STATUS_POSSIBLE_DEADLOCK\n#define STATUS_POSSIBLE_DEADLOCK 0xC0000194uL\n#endif\n\nrt_debug_if rt_debug = {\n    .verbosity = {\n        .level   =  0,\n        .quiet   =  0,\n        .info    =  1,\n        .verbose =  2,\n        .debug   =  3,\n        .trace   =  4,\n    },\n    .verbosity_from_string = rt_debug_verbosity_from_string,\n    .tee                   = null,\n    .output                = rt_debug_output,\n    .println               = rt_debug_println,\n    .println_va            = rt_debug_println_va,\n    .perrno                = rt_debug_perrno,\n    .perror                = rt_debug_perror,\n    .is_debugger_present   = rt_debug_is_debugger_present,\n    .breakpoint            = rt_debug_breakpoint,\n    .raise                 = rt_debug_raise,\n    .exception             = {\n        .access_violation        = EXCEPTION_ACCESS_VIOLATION,\n        .datatype_misalignment   = EXCEPTION_DATATYPE_MISALIGNMENT,\n        .breakpoint              = EXCEPTION_BREAKPOINT,\n        .single_step             = EXCEPTION_SINGLE_STEP,\n        .array_bounds            = EXCEPTION_ARRAY_BOUNDS_EXCEEDED,\n        .float_denormal_operand  = EXCEPTION_FLT_DENORMAL_OPERAND,\n        .float_divide_by_zero    = EXCEPTION_FLT_DIVIDE_BY_ZERO,\n        .float_inexact_result    = EXCEPTION_FLT_INEXACT_RESULT,\n        .float_invalid_operation = EXCEPTION_FLT_INVALID_OPERATION,\n        .float_overflow          = EXCEPTION_FLT_OVERFLOW,\n        .float_stack_check       = EXCEPTION_FLT_STACK_CHECK,\n        .float_underflow         = EXCEPTION_FLT_UNDERFLOW,\n        .int_divide_by_zero      = EXCEPTION_INT_DIVIDE_BY_ZERO,\n        .int_overflow            = EXCEPTION_INT_OVERFLOW,\n        .priv_instruction        = EXCEPTION_PRIV_INSTRUCTION,\n        .in_page_error           = EXCEPTION_IN_PAGE_ERROR,\n        .illegal_instruction     = EXCEPTION_ILLEGAL_INSTRUCTION,\n        .noncontinuable          = EXCEPTION_NONCONTINUABLE_EXCEPTION,\n        .stack_overflow          = EXCEPTION_STACK_OVERFLOW,\n        .invalid_disposition     = EXCEPTION_INVALID_DISPOSITION,\n        .guard_page              = EXCEPTION_GUARD_PAGE,\n        .invalid_handle          = EXCEPTION_INVALID_HANDLE,\n        .possible_deadlock       = EXCEPTION_POSSIBLE_DEADLOCK\n    },\n    .test                  = rt_debug_test\n};\n\n// ________________________________ rt_files.c ________________________________\n\n// TODO: test FILE_APPEND_DATA\n// https://learn.microsoft.com/en-us/windows/win32/fileio/appending-one-file-to-another-file?redirectedfrom=MSDN\n\n// are posix and Win32 seek in agreement?\nrt_static_assertion(SEEK_SET == FILE_BEGIN);\nrt_static_assertion(SEEK_CUR == FILE_CURRENT);\nrt_static_assertion(SEEK_END == FILE_END);\n\n#ifndef O_SYNC\n#define O_SYNC (0x10000)\n#endif\n\nstatic errno_t rt_files_open(rt_file_t* *file, const char* fn, int32_t f) {\n    DWORD access = (f & rt_files.o_wr) ? GENERIC_WRITE :\n                   (f & rt_files.o_rw) ? GENERIC_READ | GENERIC_WRITE :\n                                      GENERIC_READ;\n    access |= (f & rt_files.o_append) ? FILE_APPEND_DATA : 0;\n    DWORD disposition =\n        (f & rt_files.o_create) ? ((f & rt_files.o_excl)  ? CREATE_NEW :\n                                (f & rt_files.o_trunc) ? CREATE_ALWAYS :\n                                                      OPEN_ALWAYS) :\n            (f & rt_files.o_trunc) ? TRUNCATE_EXISTING : OPEN_EXISTING;\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    DWORD attr = FILE_ATTRIBUTE_NORMAL;\n    attr |= (f & O_SYNC) ? FILE_FLAG_WRITE_THROUGH : 0;\n    *file = CreateFileA(fn, access, share, null, disposition, attr, null);\n    return *file != INVALID_HANDLE_VALUE ? 0 : rt_core.err();\n}\n\nstatic bool rt_files_is_valid(rt_file_t* file) { // both null and rt_files.invalid\n    return file != rt_files.invalid && file != null;\n}\n\nstatic errno_t rt_files_seek(rt_file_t* file, int64_t *position, int32_t method) {\n    LARGE_INTEGER distance_to_move = { .QuadPart = *position };\n    LARGE_INTEGER p = { 0 }; // pointer\n    errno_t r = rt_b2e(SetFilePointerEx(file, distance_to_move, &p, (DWORD)method));\n    if (r == 0) { *position = p.QuadPart; }\n    return r;\n}\n\nstatic inline uint64_t rt_files_ft_to_us(FILETIME ft) { // us (microseconds)\n    return (ft.dwLowDateTime | (((uint64_t)ft.dwHighDateTime) << 32)) / 10;\n}\n\nstatic int64_t rt_files_a2t(DWORD a) {\n    int64_t type = 0;\n    if (a & FILE_ATTRIBUTE_REPARSE_POINT) {\n        type |= rt_files.type_symlink;\n    }\n    if (a & FILE_ATTRIBUTE_DIRECTORY) {\n        type |= rt_files.type_folder;\n    }\n    if (a & FILE_ATTRIBUTE_DEVICE) {\n        type |= rt_files.type_device;\n    }\n    return type;\n}\n\n#ifdef FILES_LINUX_PATH_BY_FD\n\nstatic int get_final_path_name_by_fd(int fd, char *buffer, int32_t bytes) {\n    swear(bytes >= 0);\n    char fd_path[16 * 1024];\n    // /proc/self/fd/* is a symbolic link\n    snprintf(fd_path, sizeof(fd_path), \"/proc/self/fd/%d\", fd);\n    size_t len = readlink(fd_path, buffer, bytes - 1);\n    if (len != -1) { buffer[len] = 0x00; } // Null-terminate the result\n    return len == -1 ? errno : 0;\n}\n\n#endif\n\nstatic errno_t rt_files_stat(rt_file_t* file, rt_files_stat_t* s,\n                             bool follow_symlink) {\n    errno_t r = 0;\n    BY_HANDLE_FILE_INFORMATION fi;\n    rt_fatal_win32err(GetFileInformationByHandle(file, &fi));\n    const bool symlink =\n        (fi.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;\n    if (follow_symlink && symlink) {\n        const DWORD flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;\n        DWORD n = GetFinalPathNameByHandleA(file, null, 0, flags);\n        if (n == 0) {\n            r = rt_core.err();\n        } else {\n            char* name = null;\n            r = rt_heap.allocate(null, (void**)&name, (int64_t)n + 2, false);\n            if (r == 0) {\n                n = GetFinalPathNameByHandleA(file, name, n + 1, flags);\n                if (n == 0) {\n                    r = rt_core.err();\n                } else {\n                    rt_file_t* f = rt_files.invalid;\n                    r = rt_files.open(&f, name, rt_files.o_rd);\n                    if (r == 0) { // keep following:\n                        r = rt_files.stat(f, s, follow_symlink);\n                        rt_files.close(f);\n                    }\n                }\n                rt_heap.deallocate(null, name);\n            }\n        }\n    } else {\n        s->size = (int64_t)((uint64_t)fi.nFileSizeLow |\n                          (((uint64_t)fi.nFileSizeHigh) << 32));\n        s->created  = rt_files_ft_to_us(fi.ftCreationTime); // since epoch\n        s->accessed = rt_files_ft_to_us(fi.ftLastAccessTime);\n        s->updated  = rt_files_ft_to_us(fi.ftLastWriteTime);\n        s->type = rt_files_a2t(fi.dwFileAttributes);\n    }\n    return r;\n}\n\nstatic errno_t rt_files_read(rt_file_t* file, void* data, int64_t bytes, int64_t *transferred) {\n    errno_t r = 0;\n    *transferred = 0;\n    while (bytes > 0 && r == 0) {\n        DWORD chunk_size = (DWORD)(bytes > UINT32_MAX ? UINT32_MAX : bytes);\n        DWORD bytes_read = 0;\n        r = rt_b2e(ReadFile(file, data, chunk_size, &bytes_read, null));\n        if (r == 0) {\n            *transferred += bytes_read;\n            bytes -= bytes_read;\n            data = (uint8_t*)data + bytes_read;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_files_write(rt_file_t* file, const void* data, int64_t bytes, int64_t *transferred) {\n    errno_t r = 0;\n    *transferred = 0;\n    while (bytes > 0 && r == 0) {\n        DWORD chunk_size = (DWORD)(bytes > UINT32_MAX ? UINT32_MAX : bytes);\n        DWORD bytes_read = 0;\n        r = rt_b2e(WriteFile(file, data, chunk_size, &bytes_read, null));\n        if (r == 0) {\n            *transferred += bytes_read;\n            bytes -= bytes_read;\n            data = (const uint8_t*)data + bytes_read;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_files_flush(rt_file_t* file) {\n    return rt_b2e(FlushFileBuffers(file));\n}\n\nstatic void rt_files_close(rt_file_t* file) {\n    rt_win32_close_handle(file);\n}\n\nstatic errno_t rt_files_write_fully(const char* filename, const void* data,\n                                 int64_t bytes, int64_t *transferred) {\n    if (transferred != null) { *transferred = 0; }\n    errno_t r = 0;\n    const DWORD access = GENERIC_WRITE;\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    const DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH;\n    HANDLE file = CreateFileA(filename, access, share, null, CREATE_ALWAYS,\n                              flags, null);\n    if (file == INVALID_HANDLE_VALUE) {\n        r = rt_core.err();\n    } else {\n        int64_t written = 0;\n        const uint8_t* p = (const uint8_t*)data;\n        while (r == 0 && bytes > 0) {\n            uint64_t write = bytes >= UINT32_MAX ?\n                (uint64_t)(UINT32_MAX) - 0xFFFFuLL : (uint64_t)bytes;\n            rt_assert(0 < write && write < (uint64_t)UINT32_MAX);\n            DWORD chunk = 0;\n            r = rt_b2e(WriteFile(file, p, (DWORD)write, &chunk, null));\n            written += chunk;\n            bytes -= chunk;\n        }\n        if (transferred != null) { *transferred = written; }\n        errno_t rc = rt_b2e(FlushFileBuffers(file));\n        if (r == 0) { r = rc; }\n        rt_win32_close_handle(file);\n    }\n    return r;\n}\n\nstatic errno_t rt_files_unlink(const char* pathname) {\n    if (rt_files.is_folder(pathname)) {\n        return rt_b2e(RemoveDirectoryA(pathname));\n    } else {\n        return rt_b2e(DeleteFileA(pathname));\n    }\n}\n\nstatic errno_t rt_files_create_tmp(char* fn, int32_t count) {\n    // create temporary file (not folder!) see folders_test() about racing\n    rt_swear(fn != null && count > 0);\n    const char* tmp = rt_files.tmp();\n    errno_t r = 0;\n    if (count < (int32_t)strlen(tmp) + 8) {\n        r = ERROR_BUFFER_OVERFLOW;\n    } else {\n        rt_assert(count > (int32_t)strlen(tmp) + 8);\n        // If GetTempFileNameA() succeeds, the return value is the length,\n        // in chars, of the string copied to lpBuffer, not including the\n        // terminating null character.If the function fails,\n        // the return value is zero.\n        if (count > (int32_t)strlen(tmp) + 8) {\n            char prefix[4] = { 0 };\n            r = GetTempFileNameA(tmp, prefix, 0, fn) == 0 ? rt_core.err() : 0;\n            if (r == 0) {\n                rt_assert(rt_files.exists(fn) && !rt_files.is_folder(fn));\n            } else {\n                rt_println(\"GetTempFileNameA() failed %s\", rt_strerr(r));\n            }\n        } else {\n            r = ERROR_BUFFER_OVERFLOW;\n        }\n    }\n    return r;\n}\n\n#pragma push_macro(\"files_acl_args\")\n#pragma push_macro(\"files_get_acl\")\n#pragma push_macro(\"files_set_acl\")\n\n#define rt_files_acl_args(acl) DACL_SECURITY_INFORMATION, null, null, acl, null\n\n#define rt_files_get_acl(obj, type, acl, sd) (errno_t)(         \\\n    (type == SE_FILE_OBJECT ? GetNamedSecurityInfoA((char*)obj, \\\n             SE_FILE_OBJECT, rt_files_acl_args(acl), &sd) :     \\\n    (type == SE_KERNEL_OBJECT) ? GetSecurityInfo((HANDLE)obj,   \\\n             SE_KERNEL_OBJECT, rt_files_acl_args(acl), &sd) :   \\\n    ERROR_INVALID_PARAMETER))\n\n#define rt_files_set_acl(obj, type, acl) (errno_t)(             \\\n    (type == SE_FILE_OBJECT ? SetNamedSecurityInfoA((char*)obj, \\\n             SE_FILE_OBJECT, rt_files_acl_args(acl)) :          \\\n    (type == SE_KERNEL_OBJECT) ? SetSecurityInfo((HANDLE)obj,   \\\n             SE_KERNEL_OBJECT, rt_files_acl_args(acl)) :        \\\n    ERROR_INVALID_PARAMETER))\n\nstatic errno_t rt_files_acl_add_ace(ACL* acl, SID* sid, uint32_t mask,\n                                 ACL** free_me, byte flags) {\n    ACL_SIZE_INFORMATION info = {0};\n    ACL* bigger = null;\n    uint32_t bytes_needed = sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(sid)\n                          - sizeof(DWORD);\n    errno_t r = rt_b2e(GetAclInformation(acl, &info, sizeof(ACL_SIZE_INFORMATION),\n        AclSizeInformation));\n    if (r == 0 && info.AclBytesFree < bytes_needed) {\n        const int64_t bytes = (int64_t)(info.AclBytesInUse + bytes_needed);\n        r = rt_heap.allocate(null, (void**)&bigger, bytes, true);\n        if (r == 0) {\n            r = rt_b2e(InitializeAcl((ACL*)bigger,\n                    info.AclBytesInUse + bytes_needed, ACL_REVISION));\n        }\n    }\n    if (r == 0 && bigger != null) {\n        for (int32_t i = 0; i < (int32_t)info.AceCount; i++) {\n            ACCESS_ALLOWED_ACE* ace = null;\n            r = rt_b2e(GetAce(acl, (DWORD)i, (void**)&ace));\n            if (r != 0) { break; }\n            r = rt_b2e(AddAce(bigger, ACL_REVISION, MAXDWORD, ace,\n                           ace->Header.AceSize));\n            if (r != 0) { break; }\n        }\n    }\n    if (r == 0) {\n        ACCESS_ALLOWED_ACE* ace = null;\n        r = rt_heap.allocate(null, (void**)&ace, bytes_needed, true);\n        if (r == 0) {\n            ace->Header.AceFlags = flags;\n            ace->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;\n            ace->Header.AceSize = (WORD)bytes_needed;\n            ace->Mask = mask;\n            ace->SidStart = sizeof(ACCESS_ALLOWED_ACE);\n            memcpy(&ace->SidStart, sid, GetLengthSid(sid));\n            r = rt_b2e(AddAce(bigger != null ? bigger : acl, ACL_REVISION, MAXDWORD,\n                           ace, bytes_needed));\n            rt_heap.deallocate(null, ace);\n        }\n    }\n    *free_me = bigger;\n    return r;\n}\n\nstatic errno_t rt_files_lookup_sid(ACCESS_ALLOWED_ACE* ace) {\n    // handy for debugging\n    SID* sid = (SID*)&ace->SidStart;\n    DWORD l1 = 128, l2 = 128;\n    char account[128];\n    char group[128];\n    SID_NAME_USE use;\n    errno_t r = rt_b2e(LookupAccountSidA(null, sid, account,\n                                     &l1, group, &l2, &use));\n    if (r == 0) {\n        rt_println(\"%s/%s: type: %d, mask: 0x%X, flags:%d\",\n                group, account,\n                ace->Header.AceType, ace->Mask, ace->Header.AceFlags);\n    } else {\n        rt_println(\"LookupAccountSidA() failed %s\", rt_strerr(r));\n    }\n    return r;\n}\n\nstatic errno_t rt_files_add_acl_ace(void* obj, int32_t obj_type,\n                                 int32_t sid_type, uint32_t mask) {\n    uint8_t stack[SECURITY_MAX_SID_SIZE] = {0};\n    DWORD n = rt_countof(stack);\n    SID* sid = (SID*)stack;\n    errno_t r = rt_b2e(CreateWellKnownSid((WELL_KNOWN_SID_TYPE)sid_type,\n                                       null, sid, &n));\n    if (r != 0) {\n        return ERROR_INVALID_PARAMETER;\n    }\n    ACL* acl = null;\n    void* sd = null;\n    r = rt_files_get_acl(obj, obj_type, &acl, sd);\n    if (r == 0) {\n        ACCESS_ALLOWED_ACE* found = null;\n        for (int32_t i = 0; i < acl->AceCount; i++) {\n            ACCESS_ALLOWED_ACE* ace = null;\n            r = rt_b2e(GetAce(acl, (DWORD)i, (void**)&ace));\n            if (r != 0) { break; }\n            if (EqualSid((SID*)&ace->SidStart, sid)) {\n                if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE &&\n                   (ace->Header.AceFlags & INHERITED_ACE) == 0) {\n                    found = ace;\n                } else if (ace->Header.AceType !=\n                           ACCESS_ALLOWED_ACE_TYPE) {\n                    rt_println(\"%d ACE_TYPE is not supported.\",\n                             ace->Header.AceType);\n                    r = ERROR_INVALID_PARAMETER;\n                }\n                break;\n            }\n        }\n        if (r == 0 && found) {\n            if ((found->Mask & mask) != mask) {\n//              rt_println(\"updating existing ace\");\n                found->Mask |= mask;\n                r = rt_files_set_acl(obj, obj_type, acl);\n            } else {\n//              rt_println(\"desired access is already allowed by ace\");\n            }\n        } else if (r == 0) {\n//          rt_println(\"inserting new ace\");\n            ACL* new_acl = null;\n            byte flags = obj_type == SE_FILE_OBJECT ?\n                CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;\n            r = rt_files_acl_add_ace(acl, sid, mask, &new_acl, flags);\n            if (r == 0) {\n                r = rt_files_set_acl(obj, obj_type, (new_acl != null ? new_acl : acl));\n            }\n            if (new_acl != null) { rt_heap.deallocate(null, new_acl); }\n        }\n    }\n    if (sd != null) { LocalFree(sd); }\n    return r;\n}\n\n#pragma pop_macro(\"files_set_acl\")\n#pragma pop_macro(\"files_get_acl\")\n#pragma pop_macro(\"files_acl_args\")\n\nstatic errno_t rt_files_chmod777(const char* pathname) {\n    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;\n    PSID everyone = null; // Create a well-known SID for the Everyone group.\n    rt_fatal_win32err(AllocateAndInitializeSid(&SIDAuthWorld, 1,\n             SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyone));\n    EXPLICIT_ACCESSA ea[1] = { { 0 } };\n    // Initialize an EXPLICIT_ACCESS structure for an ACE.\n    ea[0].grfAccessPermissions = 0xFFFFFFFF;\n    ea[0].grfAccessMode  = GRANT_ACCESS; // The ACE will allow everyone all access.\n    ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;\n    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;\n    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;\n    ea[0].Trustee.ptstrName  = (LPSTR)everyone;\n    // Create a new ACL that contains the new ACEs.\n    ACL* acl = null;\n    rt_fatal_if_error(SetEntriesInAclA(1, ea, null, &acl));\n    // Initialize a security descriptor.\n    uint8_t stack[SECURITY_DESCRIPTOR_MIN_LENGTH] = {0};\n    SECURITY_DESCRIPTOR* sd = (SECURITY_DESCRIPTOR*)stack;\n    rt_fatal_win32err(InitializeSecurityDescriptor(sd,\n        SECURITY_DESCRIPTOR_REVISION));\n    // Add the ACL to the security descriptor.\n    rt_fatal_win32err(SetSecurityDescriptorDacl(sd,\n        /* present flag: */ true, acl, /* not a default DACL: */  false));\n    // Change the security attributes\n    errno_t r = rt_b2e(SetFileSecurityA(pathname, DACL_SECURITY_INFORMATION, sd));\n    if (r != 0) {\n        rt_println(\"chmod777(%s) failed %s\", pathname, rt_strerr(r));\n    }\n    if (everyone != null) { FreeSid(everyone); }\n    if (acl != null) { LocalFree(acl); }\n    return r;\n}\n\n// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectorya\n// \"If lpSecurityAttributes is null, the directory gets a default security\n//  descriptor. The ACLs in the default security descriptor for a directory\n//  are inherited from its parent directory.\"\n\nstatic errno_t rt_files_mkdirs(const char* dir) {\n    const int32_t n = (int32_t)strlen(dir) + 1;\n    char* s = null;\n    errno_t r = rt_heap.allocate(null, (void**)&s, n, true);\n    const char* next = strchr(dir, '\\\\');\n    if (next == null) { next = strchr(dir, '/'); }\n    while (r == 0 && next != null) {\n        if (next > dir && *(next - 1) != ':') {\n            memcpy(s, dir, (size_t)(next - dir));\n            r = rt_b2e(CreateDirectoryA(s, null));\n            if (r == ERROR_ALREADY_EXISTS) { r = 0; }\n        }\n        if (r == 0) {\n            const char* prev = ++next;\n            next = strchr(prev, '\\\\');\n            if (next == null) { next = strchr(prev, '/'); }\n        }\n    }\n    if (r == 0) {\n        r = rt_b2e(CreateDirectoryA(dir, null));\n    }\n    rt_heap.deallocate(null, s);\n    return r == ERROR_ALREADY_EXISTS ? 0 : r;\n}\n\n#pragma push_macro(\"rt_files_realloc_path\")\n#pragma push_macro(\"rt_files_append_name\")\n\n#define rt_files_realloc_path(r, pn, pnc, fn, name) do {                \\\n    const int32_t bytes = (int32_t)(strlen(fn) + strlen(name) + 3);     \\\n    if (bytes > pnc) {                                                  \\\n        r = rt_heap.reallocate(null, (void**)&pn, bytes, false);        \\\n        if (r != 0) {                                                   \\\n            pnc = bytes;                                                \\\n        } else {                                                        \\\n            rt_heap.deallocate(null, pn);                               \\\n            pn = null;                                                  \\\n        }                                                               \\\n    }                                                                   \\\n} while (0)\n\n#define rt_files_append_name(pn, pnc, fn, name) do {     \\\n    if (strcmp(fn, \"\\\\\") == 0 || strcmp(fn, \"/\") == 0) { \\\n        rt_str.format(pn, pnc, \"\\\\%s\", name);            \\\n    } else {                                             \\\n        rt_str.format(pn, pnc, \"%.*s\\\\%s\", k, fn, name); \\\n    }                                                    \\\n} while (0)\n\nstatic errno_t rt_files_rmdirs(const char* fn) {\n    rt_files_stat_t st;\n    rt_folder_t folder;\n    errno_t r = rt_files.opendir(&folder, fn);\n    if (r == 0) {\n        int32_t k = (int32_t)strlen(fn);\n        // remove trailing backslash (except if it is root: \"/\" or \"\\\\\")\n        if (k > 1 && (fn[k - 1] == '/' || fn[k - 1] == '\\\\')) {\n            k--;\n        }\n        int32_t pnc = 64 * 1024; // pathname \"pn\" capacity in bytes\n        char* pn = null;\n        r = rt_heap.allocate(null, (void**)&pn, pnc, false);\n        while (r == 0) {\n            // recurse into sub folders and remove them first\n            // do NOT follow symlinks - it could be disastrous\n            const char* name = rt_files.readdir(&folder, &st);\n            if (name == null) { break; }\n            if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0 &&\n                (st.type & rt_files.type_symlink) == 0 &&\n                (st.type & rt_files.type_folder) != 0) {\n                rt_files_realloc_path(r, pn, pnc, fn, name);\n                if (r == 0) {\n                    rt_files_append_name(pn, pnc, fn, name);\n                    r = rt_files.rmdirs(pn);\n                }\n            }\n        }\n        rt_files.closedir(&folder);\n        r = rt_files.opendir(&folder, fn);\n        while (r == 0) {\n            const char* name = rt_files.readdir(&folder, &st);\n            if (name == null) { break; }\n            // symlinks are already removed as normal files\n            if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0 &&\n                (st.type & rt_files.type_folder) == 0) {\n                rt_files_realloc_path(r, pn, pnc, fn, name);\n                if (r == 0) {\n                    rt_files_append_name(pn, pnc, fn, name);\n                    r = rt_files.unlink(pn);\n                    if (r != 0) {\n                        rt_println(\"remove(%s) failed %s\", pn, rt_strerr(r));\n                    }\n                }\n            }\n        }\n        rt_heap.deallocate(null, pn);\n        rt_files.closedir(&folder);\n    }\n    if (r == 0) { r = rt_files.unlink(fn); }\n    return r;\n}\n\n#pragma pop_macro(\"rt_files_append_name\")\n#pragma pop_macro(\"rt_files_realloc_path\")\n\nstatic bool rt_files_exists(const char* path) {\n    return PathFileExistsA(path);\n}\n\nstatic bool rt_files_is_folder(const char* path) {\n    return PathIsDirectoryA(path);\n}\n\nstatic bool rt_files_is_symlink(const char* filename) {\n    DWORD attributes = GetFileAttributesA(filename);\n    return attributes != INVALID_FILE_ATTRIBUTES &&\n          (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;\n}\n\nstatic const char* rt_files_basename(const char* pathname) {\n    const char* bn = strrchr(pathname, '\\\\');\n    if (bn == null) { bn = strrchr(pathname, '/'); }\n    return bn != null ? bn + 1 : pathname;\n}\n\nstatic errno_t rt_files_copy(const char* s, const char* d) {\n    return rt_b2e(CopyFileA(s, d, false));\n}\n\nstatic errno_t rt_files_move(const char* s, const char* d) {\n    static const DWORD flags =\n        MOVEFILE_REPLACE_EXISTING |\n        MOVEFILE_COPY_ALLOWED |\n        MOVEFILE_WRITE_THROUGH;\n    return rt_b2e(MoveFileExA(s, d, flags));\n}\n\nstatic errno_t rt_files_link(const char* from, const char* to) {\n    // note reverse order of parameters:\n    return rt_b2e(CreateHardLinkA(to, from, null));\n}\n\nstatic errno_t rt_files_symlink(const char* from, const char* to) {\n    // The correct order of parameters for CreateSymbolicLinkA is:\n    // CreateSymbolicLinkA(symlink_to_create, existing_file, flags);\n    DWORD flags = rt_files.is_folder(from) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;\n    return rt_b2e(CreateSymbolicLinkA(to, from, flags));\n}\n\nstatic const char* rt_files_known_folder(int32_t kf) {\n    // known folder ids order must match enum see:\n    static const GUID* kf_ids[] = {\n        &FOLDERID_Profile,\n        &FOLDERID_Desktop,\n        &FOLDERID_Documents,\n        &FOLDERID_Downloads,\n        &FOLDERID_Music,\n        &FOLDERID_Pictures,\n        &FOLDERID_Videos,\n        &FOLDERID_Public,\n        &FOLDERID_ProgramFiles,\n        &FOLDERID_ProgramData\n    };\n    static rt_file_name_t known_folders[rt_countof(kf_ids)];\n    rt_fatal_if(!(0 <= kf && kf < rt_countof(kf_ids)), \"invalid kf=%d\", kf);\n    if (known_folders[kf].s[0] == 0) {\n        uint16_t* path = null;\n        rt_fatal_if_error(SHGetKnownFolderPath(kf_ids[kf], 0, null, &path));\n        const int32_t n = rt_countof(known_folders[kf].s);\n        rt_str.utf16to8(known_folders[kf].s, n, path, -1);\n        CoTaskMemFree(path);\n\t}\n    return known_folders[kf].s;\n}\n\nstatic const char* rt_files_bin(void) {\n    return rt_files_known_folder(rt_files.folder.bin);\n}\n\nstatic const char* rt_files_data(void) {\n    return rt_files_known_folder(rt_files.folder.data);\n}\n\nstatic const char* rt_files_tmp(void) {\n    static char tmp[rt_files_max_path];\n    if (tmp[0] == 0) {\n        // If GetTempPathA() succeeds, the return value is the length,\n        // in chars, of the string copied to lpBuffer, not including\n        // the terminating null character. If the function fails, the\n        // return value is zero.\n        errno_t r = GetTempPathA(rt_countof(tmp), tmp) == 0 ? rt_core.err() : 0;\n        rt_fatal_if(r != 0, \"GetTempPathA() failed %s\", rt_strerr(r));\n    }\n    return tmp;\n}\n\nstatic errno_t rt_files_cwd(char* fn, int32_t count) {\n    rt_swear(count > 1);\n    DWORD bytes = (DWORD)(count - 1);\n    errno_t r = rt_b2e(GetCurrentDirectoryA(bytes, fn));\n    fn[count - 1] = 0; // always\n    return r;\n}\n\nstatic errno_t rt_files_chdir(const char* fn) {\n    return rt_b2e(SetCurrentDirectoryA(fn));\n}\n\ntypedef struct rt_files_dir_s {\n    HANDLE handle;\n    WIN32_FIND_DATAA find; // On Win64: 320 bytes\n} rt_files_dir_t;\n\nrt_static_assertion(sizeof(rt_files_dir_t) <= sizeof(rt_folder_t));\n\nstatic errno_t rt_files_opendir(rt_folder_t* folder, const char* folder_name) {\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    int32_t n = (int32_t)strlen(folder_name);\n    char* fn = null;\n    // extra room for \"\\*\" suffix\n    errno_t r = rt_heap.allocate(null, (void**)&fn, (int64_t)n + 3, false);\n    if (r == 0) {\n        rt_str.format(fn, n + 3, \"%s\\\\*\", folder_name);\n        fn[n + 2] = 0;\n        d->handle = FindFirstFileA(fn, &d->find);\n        if (d->handle == INVALID_HANDLE_VALUE) { r = rt_core.err(); }\n        rt_heap.deallocate(null, fn);\n    }\n    return r;\n}\n\nstatic uint64_t rt_files_ft2us(FILETIME* ft) { // 100ns units to microseconds:\n    return (((uint64_t)ft->dwHighDateTime) << 32 | ft->dwLowDateTime) / 10;\n}\n\nstatic const char* rt_files_readdir(rt_folder_t* folder, rt_files_stat_t* s) {\n    const char* fn = null;\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    if (FindNextFileA(d->handle, &d->find)) {\n        fn = d->find.cFileName;\n        // Ensure zero termination\n        d->find.cFileName[rt_countof(d->find.cFileName) - 1] = 0x00;\n        if (s != null) {\n            s->accessed = rt_files_ft2us(&d->find.ftLastAccessTime);\n            s->created = rt_files_ft2us(&d->find.ftCreationTime);\n            s->updated = rt_files_ft2us(&d->find.ftLastWriteTime);\n            s->type = rt_files_a2t(d->find.dwFileAttributes);\n            s->size = (int64_t)((((uint64_t)d->find.nFileSizeHigh) << 32) |\n                                  (uint64_t)d->find.nFileSizeLow);\n        }\n    }\n    return fn;\n}\n\nstatic void rt_files_closedir(rt_folder_t* folder) {\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    rt_fatal_win32err(FindClose(d->handle));\n}\n\n#pragma push_macro(\"files_test_failed\")\n\n#ifdef RT_TESTS\n\n// TODO: change rt_fatal_if() to swear()\n\n#define rt_files_test_failed \" failed %s\", rt_strerr(rt_core.err())\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                       \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                                   \\\n    }                                                           \\\n} while (0)\n\nstatic void folders_dump_time(const char* label, uint64_t us) {\n    int32_t year = 0;\n    int32_t month = 0;\n    int32_t day = 0;\n    int32_t hh = 0;\n    int32_t mm = 0;\n    int32_t ss = 0;\n    int32_t ms = 0;\n    int32_t mc = 0;\n    rt_clock.local(us, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n    rt_println(\"%-7s: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d\",\n            label, year, month, day, hh, mm, ss, ms, mc);\n}\n\nstatic void folders_test(void) {\n    uint64_t now = rt_clock.microseconds(); // microseconds since epoch\n    uint64_t before = now - 1 * (uint64_t)rt_clock.usec_in_sec; // one second earlier\n    uint64_t after  = now + 2 * (uint64_t)rt_clock.usec_in_sec; // two seconds later\n    int32_t year = 0;\n    int32_t month = 0;\n    int32_t day = 0;\n    int32_t hh = 0;\n    int32_t mm = 0;\n    int32_t ss = 0;\n    int32_t ms = 0;\n    int32_t mc = 0;\n    rt_clock.local(now, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n    verbose(\"now: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d\",\n             year, month, day, hh, mm, ss, ms, mc);\n    // Test cwd, setcwd\n    const char* tmp = rt_files.tmp();\n    char cwd[256] = { 0 };\n    rt_fatal_if(rt_files.cwd(cwd, sizeof(cwd)) != 0, \"rt_files.cwd() failed\");\n    rt_fatal_if(rt_files.chdir(tmp) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                tmp, rt_strerr(rt_core.err()));\n    // there is no racing free way to create temporary folder\n    // without having a temporary file for the duration of folder usage:\n    char tmp_file[rt_files_max_path]; // create_tmp() is thread safe race free:\n    errno_t r = rt_files.create_tmp(tmp_file, rt_countof(tmp_file));\n    rt_fatal_if(r != 0, \"rt_files.create_tmp() failed %s\", rt_strerr(r));\n    char tmp_dir[rt_files_max_path];\n    rt_str_printf(tmp_dir, \"%s.dir\", tmp_file);\n    r = rt_files.mkdirs(tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.mkdirs(%s) failed %s\", tmp_dir, rt_strerr(r));\n    verbose(\"%s\", tmp_dir);\n    rt_folder_t folder;\n    char pn[rt_files_max_path] = { 0 };\n    rt_str_printf(pn, \"%s/file\", tmp_dir);\n    // cannot test symlinks because they are only\n    // available to Administrators and in Developer mode\n//  char sym[rt_files_max_path] = { 0 };\n    char hard[rt_files_max_path] = { 0 };\n    char sub[rt_files_max_path] = { 0 };\n    rt_str_printf(hard, \"%s/hard\", tmp_dir);\n    rt_str_printf(sub, \"%s/subd\", tmp_dir);\n    const char* content = \"content\";\n    int64_t transferred = 0;\n    r = rt_files.write_fully(pn, content, (int64_t)strlen(content), &transferred);\n    rt_fatal_if(r != 0, \"rt_files.write_fully(\\\"%s\\\") failed %s\", pn, rt_strerr(r));\n    rt_swear(transferred == (int64_t)strlen(content));\n    r = rt_files.link(pn, hard);\n    rt_fatal_if(r != 0, \"rt_files.link(\\\"%s\\\", \\\"%s\\\") failed %s\",\n                      pn, hard, rt_strerr(r));\n    r = rt_files.mkdirs(sub);\n    rt_fatal_if(r != 0, \"rt_files.mkdirs(\\\"%s\\\") failed %s\", sub, rt_strerr(r));\n    r = rt_files.opendir(&folder, tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.opendir(\\\"%s\\\") failed %s\", tmp_dir, rt_strerr(r));\n    for (;;) {\n        rt_files_stat_t st = { 0 };\n        const char* name = rt_files.readdir(&folder, &st);\n        if (name == null) { break; }\n        uint64_t at = st.accessed;\n        uint64_t ct = st.created;\n        uint64_t ut = st.updated;\n        rt_swear(ct <= at && ct <= ut);\n        rt_clock.local(ct, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n        bool is_folder = st.type & rt_files.type_folder;\n        bool is_symlink = st.type & rt_files.type_symlink;\n        int64_t bytes = st.size;\n        verbose(\"%s: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d %lld bytes %s%s\",\n                name, year, month, day, hh, mm, ss, ms, mc,\n                bytes, is_folder ? \"[folder]\" : \"\", is_symlink ? \"[symlink]\" : \"\");\n        if (strcmp(name, \"file\") == 0 || strcmp(name, \"hard\") == 0) {\n            rt_swear(bytes == (int64_t)strlen(content),\n                    \"size of \\\"%s\\\": %lld is incorrect expected: %d\",\n                    name, bytes, transferred);\n        }\n        if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) {\n            rt_swear(is_folder, \"\\\"%s\\\" is_folder: %d\", name, is_folder);\n        } else {\n            rt_swear((strcmp(name, \"subd\") == 0) == is_folder,\n                  \"\\\"%s\\\" is_folder: %d\", name, is_folder);\n            // empirically timestamps are imprecise on NTFS\n            rt_swear(at >= before, \"access: %lld  >= %lld\", at, before);\n            if (ct < before || ut < before || at >= after || ct >= after || ut >= after) {\n                rt_println(\"file: %s\", name);\n                folders_dump_time(\"before\", before);\n                folders_dump_time(\"create\", ct);\n                folders_dump_time(\"update\", ut);\n                folders_dump_time(\"access\", at);\n            }\n            rt_swear(ct >= before, \"create: %lld  >= %lld\", ct, before);\n            rt_swear(ut >= before, \"update: %lld  >= %lld\", ut, before);\n            // and no later than 2 seconds since folders_test()\n            rt_swear(at < after, \"access: %lld  < %lld\", at, after);\n            rt_swear(ct < after, \"create: %lld  < %lld\", ct, after);\n            rt_swear(at < after, \"update: %lld  < %lld\", ut, after);\n        }\n    }\n    rt_files.closedir(&folder);\n    r = rt_files.rmdirs(tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.rmdirs(\\\"%s\\\") failed %s\",\n                     tmp_dir, rt_strerr(r));\n    r = rt_files.unlink(tmp_file);\n    rt_fatal_if(r != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                     tmp_file, rt_strerr(r));\n    rt_fatal_if(rt_files.chdir(cwd) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n             cwd, rt_strerr(rt_core.err()));\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"verbose\")\n\nstatic void rt_files_test_append_thread(void* p) {\n    rt_file_t* f = (rt_file_t*)p;\n    uint8_t data[256] = {0};\n    for (int i = 0; i < 256; i++) { data[i] = (uint8_t)i; }\n    int64_t transferred = 0;\n    rt_fatal_if(rt_files.write(f, data, rt_countof(data), &transferred) != 0 ||\n             transferred != rt_countof(data), \"rt_files.write()\" rt_files_test_failed);\n}\n\nstatic void rt_files_test(void) {\n    folders_test();\n    uint64_t now = rt_clock.microseconds(); // epoch time\n    char tf[256]; // temporary file\n    rt_fatal_if(rt_files.create_tmp(tf, rt_countof(tf)) != 0,\n            \"rt_files.create_tmp()\" rt_files_test_failed);\n    uint8_t data[256] = {0};\n    int64_t transferred = 0;\n    for (int i = 0; i < 256; i++) { data[i] = (uint8_t)i; }\n    {\n        rt_file_t* f = rt_files.invalid;\n        rt_fatal_if(rt_files.open(&f, tf,\n                 rt_files.o_wr | rt_files.o_create | rt_files.o_trunc) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_fatal_if(rt_files.write_fully(tf, data, rt_countof(data), &transferred) != 0 ||\n                 transferred != rt_countof(data),\n                \"rt_files.write_fully()\" rt_files_test_failed);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rd) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        for (int32_t i = 0; i < 256; i++) {\n            for (int32_t j = 1; j < 256 - i; j++) {\n                uint8_t test[rt_countof(data)] = { 0 };\n                int64_t position = i;\n                rt_fatal_if(rt_files.seek(f, &position, rt_files.seek_set) != 0 ||\n                         position != i,\n                        \"rt_files.seek(position: %lld) failed %s\",\n                         position, rt_strerr(rt_core.err()));\n                rt_fatal_if(rt_files.read(f, test, j, &transferred) != 0 ||\n                         transferred != j,\n                        \"rt_files.read() transferred: %lld failed %s\",\n                        transferred, rt_strerr(rt_core.err()));\n                for (int32_t k = 0; k < j; k++) {\n                    rt_swear(test[k] == data[i + k],\n                         \"Data mismatch at position: %d, length %d\"\n                         \"test[%d]: 0x%02X != data[%d + %d]: 0x%02X \",\n                          i, j,\n                          k, test[k], i, k, data[i + k]);\n                }\n            }\n        }\n        rt_swear((rt_files.o_rd | rt_files.o_wr) != rt_files.o_rw);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rw) != 0 || !rt_files.is_valid(f),\n                \"rt_files.open()\" rt_files_test_failed);\n        for (int32_t i = 0; i < 256; i++) {\n            uint8_t val = ~data[i];\n            int64_t pos = i;\n            rt_fatal_if(rt_files.seek(f, &pos, rt_files.seek_set) != 0 || pos != i,\n                    \"rt_files.seek() failed %s\", rt_core.err());\n            rt_fatal_if(rt_files.write(f, &val, 1, &transferred) != 0 ||\n                     transferred != 1, \"rt_files.write()\" rt_files_test_failed);\n            pos = i;\n            rt_fatal_if(rt_files.seek(f, &pos, rt_files.seek_set) != 0 || pos != i,\n                    \"rt_files.seek(pos: %lld i: %d) failed %s\", pos, i, rt_core.err());\n            uint8_t read_val = 0;\n            rt_fatal_if(rt_files.read(f, &read_val, 1, &transferred) != 0 ||\n                     transferred != 1, \"rt_files.read()\" rt_files_test_failed);\n            rt_swear(read_val == val, \"Data mismatch at position %d\", i);\n        }\n        rt_files_stat_t s = { 0 };\n        rt_files.stat(f, &s, false);\n        uint64_t before = now - 1 * (uint64_t)rt_clock.usec_in_sec; // one second before now\n        uint64_t after  = now + 2 * (uint64_t)rt_clock.usec_in_sec; // two seconds after\n        rt_swear(before <= s.created  && s.created  <= after,\n             \"before: %lld created: %lld after: %lld\", before, s.created, after);\n        rt_swear(before <= s.accessed && s.accessed <= after,\n             \"before: %lld created: %lld accessed: %lld\", before, s.accessed, after);\n        rt_swear(before <= s.updated  && s.updated  <= after,\n             \"before: %lld created: %lld updated: %lld\", before, s.updated, after);\n        rt_files.close(f);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_wr | rt_files.o_create | rt_files.o_trunc) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_files.stat(f, &s, false);\n        rt_swear(s.size == 0, \"File is not empty after truncation. .size: %lld\", s.size);\n        rt_files.close(f);\n    }\n    {  // Append test with threads\n        rt_file_t* f = rt_files.invalid;\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rw | rt_files.o_append) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_thread_t thread1 = rt_thread.start(rt_files_test_append_thread, f);\n        rt_thread_t thread2 = rt_thread.start(rt_files_test_append_thread, f);\n        rt_thread.join(thread1, -1);\n        rt_thread.join(thread2, -1);\n        rt_files.close(f);\n    }\n    {   // write_fully, exists, is_folder, mkdirs, rmdirs, create_tmp, chmod777\n        rt_fatal_if(rt_files.write_fully(tf, data, rt_countof(data), &transferred) != 0 ||\n                 transferred != rt_countof(data),\n                \"rt_files.write_fully() failed %s\", rt_core.err());\n        rt_fatal_if(!rt_files.exists(tf), \"file \\\"%s\\\" does not exist\", tf);\n        rt_fatal_if(rt_files.is_folder(tf), \"%s is a folder\", tf);\n        rt_fatal_if(rt_files.chmod777(tf) != 0, \"rt_files.chmod777(\\\"%s\\\") failed %s\",\n                 tf, rt_strerr(rt_core.err()));\n        char folder[256] = { 0 };\n        rt_str_printf(folder, \"%s.folder\\\\subfolder\", tf);\n        rt_fatal_if(rt_files.mkdirs(folder) != 0, \"rt_files.mkdirs(\\\"%s\\\") failed %s\",\n            folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.is_folder(folder), \"\\\"%s\\\" is not a folder\", folder);\n        rt_fatal_if(rt_files.chmod777(folder) != 0, \"rt_files.chmod777(\\\"%s\\\") failed %s\",\n                 folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.rmdirs(folder) != 0, \"rt_files.rmdirs(\\\"%s\\\") failed %s\",\n                 folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(folder), \"folder \\\"%s\\\" still exists\", folder);\n    }\n    {   // getcwd, chdir\n        const char* tmp = rt_files.tmp();\n        char cwd[256] = { 0 };\n        rt_fatal_if(rt_files.cwd(cwd, sizeof(cwd)) != 0, \"rt_files.cwd() failed\");\n        rt_fatal_if(rt_files.chdir(tmp) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                 tmp, rt_strerr(rt_core.err()));\n        // symlink\n        if (rt_processes.is_elevated()) {\n            char sym_link[rt_files_max_path];\n            rt_str_printf(sym_link, \"%s.sym_link\", tf);\n            rt_fatal_if(rt_files.symlink(tf, sym_link) != 0,\n                \"rt_files.symlink(\\\"%s\\\", \\\"%s\\\") failed %s\",\n                tf, sym_link, rt_strerr(rt_core.err()));\n            rt_fatal_if(!rt_files.is_symlink(sym_link), \"\\\"%s\\\" is not a sym_link\", sym_link);\n            rt_fatal_if(rt_files.unlink(sym_link) != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                    sym_link, rt_strerr(rt_core.err()));\n        } else {\n            rt_println(\"Skipping rt_files.symlink test: process is not elevated\");\n        }\n        // hard link\n        char hard_link[rt_files_max_path];\n        rt_str_printf(hard_link, \"%s.hard_link\", tf);\n        rt_fatal_if(rt_files.link(tf, hard_link) != 0,\n            \"rt_files.link(\\\"%s\\\", \\\"%s\\\") failed %s\",\n            tf, hard_link, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.exists(hard_link), \"\\\"%s\\\" does not exist\", hard_link);\n        rt_fatal_if(rt_files.unlink(hard_link) != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                 hard_link, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(hard_link), \"\\\"%s\\\" still exists\", hard_link);\n        // copy, move:\n        rt_fatal_if(rt_files.copy(tf, \"copied_file\") != 0,\n            \"rt_files.copy(\\\"%s\\\", 'copied_file') failed %s\",\n            tf, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.exists(\"copied_file\"), \"'copied_file' does not exist\");\n        rt_fatal_if(rt_files.move(\"copied_file\", \"moved_file\") != 0,\n            \"rt_files.move('copied_file', 'moved_file') failed %s\",\n            rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(\"copied_file\"), \"'copied_file' still exists\");\n        rt_fatal_if(!rt_files.exists(\"moved_file\"), \"'moved_file' does not exist\");\n        rt_fatal_if(rt_files.unlink(\"moved_file\") != 0,\n                \"rt_files.unlink('moved_file') failed %s\",\n                 rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.chdir(cwd) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                    cwd, rt_strerr(rt_core.err()));\n    }\n    rt_fatal_if(rt_files.unlink(tf) != 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_files_test(void) {}\n\n#endif // RT_TESTS\n\n#pragma pop_macro(\"files_test_failed\")\n\nrt_files_if rt_files = {\n    .invalid  = (rt_file_t*)INVALID_HANDLE_VALUE,\n    // rt_files_stat_t.type:\n    .type_folder  = 0x00000010, // FILE_ATTRIBUTE_DIRECTORY\n    .type_symlink = 0x00000400, // FILE_ATTRIBUTE_REPARSE_POINT\n    .type_device  = 0x00000040, // FILE_ATTRIBUTE_DEVICE\n    // seek() methods:\n    .seek_set = SEEK_SET,\n    .seek_cur = SEEK_CUR,\n    .seek_end = SEEK_END,\n    // open() flags: missing O_RSYNC, O_DSYNC, O_NONBLOCK, O_NOCTTY\n    .o_rd     = O_RDONLY,\n    .o_wr     = O_WRONLY,\n    .o_rw     = O_RDWR,\n    .o_append = O_APPEND,\n    .o_create = O_CREAT,\n    .o_excl   = O_EXCL,\n    .o_trunc  = O_TRUNC,\n    .o_sync   = O_SYNC,\n    // known folders ids:\n    .folder = {\n        .home      = 0, // c:\\Users\\<username>\n        .desktop   = 1,\n        .documents = 2,\n        .downloads = 3,\n        .music     = 4,\n        .pictures  = 5,\n        .videos    = 6,\n        .shared    = 7, // c:\\Users\\Public\n        .bin       = 8, // c:\\Program Files\n        .data      = 9  // c:\\ProgramData\n    },\n    // methods:\n    .open         = rt_files_open,\n    .is_valid     = rt_files_is_valid,\n    .seek         = rt_files_seek,\n    .stat         = rt_files_stat,\n    .read         = rt_files_read,\n    .write        = rt_files_write,\n    .flush        = rt_files_flush,\n    .close        = rt_files_close,\n    .write_fully  = rt_files_write_fully,\n    .exists       = rt_files_exists,\n    .is_folder    = rt_files_is_folder,\n    .is_symlink   = rt_files_is_symlink,\n    .mkdirs       = rt_files_mkdirs,\n    .rmdirs       = rt_files_rmdirs,\n    .create_tmp   = rt_files_create_tmp,\n    .chmod777     = rt_files_chmod777,\n    .unlink       = rt_files_unlink,\n    .link         = rt_files_link,\n    .symlink      = rt_files_symlink,\n    .basename     = rt_files_basename,\n    .copy         = rt_files_copy,\n    .move         = rt_files_move,\n    .cwd          = rt_files_cwd,\n    .chdir        = rt_files_chdir,\n    .known_folder = rt_files_known_folder,\n    .bin          = rt_files_bin,\n    .data         = rt_files_data,\n    .tmp          = rt_files_tmp,\n    .opendir      = rt_files_opendir,\n    .readdir      = rt_files_readdir,\n    .closedir     = rt_files_closedir,\n    .test         = rt_files_test\n};\n\n// ______________________________ rt_generics.c _______________________________\n\n#ifdef RT_TESTS\n\nstatic void rt_generics_test(void) {\n    {\n        int8_t a = 10, b = 20;\n        rt_swear(rt_max(a++, b++) == 20);\n        rt_swear(rt_min(a++, b++) == 11);\n    }\n    {\n        int32_t a = 10, b = 20;\n        rt_swear(rt_max(a++, b++) == 20);\n        rt_swear(rt_min(a++, b++) == 11);\n    }\n    {\n        fp32_t a = 1.1f, b = 2.2f;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp64_t a = 1.1, b = 2.2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp32_t a = 1.1f, b = 2.2f;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp64_t a = 1.1, b = 2.2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        char a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        unsigned char a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    // MS cl.exe version 19.39.33523 has issues with \"long\":\n    // does not pick up int32_t/uint32_t types for \"long\" and \"unsigned long\"\n    {\n        long int a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        unsigned long a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        long long a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_generics_test(void) { }\n\n#endif\n\nrt_generics_if rt_generics = {\n    .test = rt_generics_test\n};\n\n\n// ________________________________ rt_heap.c _________________________________\n\nstatic errno_t rt_heap_alloc(void* *a, int64_t bytes) {\n    return rt_heap.allocate(null, a, bytes, false);\n}\n\nstatic errno_t rt_heap_alloc_zero(void* *a, int64_t bytes) {\n    return rt_heap.allocate(null, a, bytes, true);\n}\n\nstatic errno_t rt_heap_realloc(void* *a, int64_t bytes) {\n    return rt_heap.reallocate(null, a, bytes, false);\n}\n\nstatic errno_t rt_heap_realloc_zero(void* *a, int64_t bytes) {\n    return rt_heap.reallocate(null, a, bytes, true);\n}\n\nstatic void rt_heap_free(void* a) {\n    rt_heap.deallocate(null, a);\n}\n\nstatic rt_heap_t* rt_heap_create(bool serialized) {\n    const DWORD options = serialized ? 0 : HEAP_NO_SERIALIZE;\n    return (rt_heap_t*)HeapCreate(options, 0, 0);\n}\n\nstatic void rt_heap_dispose(rt_heap_t* h) {\n    rt_fatal_win32err(HeapDestroy((HANDLE)h));\n}\n\nstatic inline HANDLE rt_heap_or_process_heap(rt_heap_t* h) {\n    static HANDLE process_heap;\n    if (process_heap == null) { process_heap = GetProcessHeap(); }\n    return h != null ? (HANDLE)h : process_heap;\n}\n\nstatic errno_t rt_heap_allocate(rt_heap_t* h, void* *p, int64_t bytes, bool zero) {\n    rt_swear(bytes > 0);\n    #ifdef DEBUG\n        static bool enabled;\n        if (!enabled) {\n            enabled = true;\n            HeapSetInformation(null, HeapEnableTerminationOnCorruption, null, 0);\n        }\n    #endif\n    const DWORD flags = zero ? HEAP_ZERO_MEMORY : 0;\n    *p = HeapAlloc(rt_heap_or_process_heap(h), flags, (SIZE_T)bytes);\n    return *p == null ? ERROR_OUTOFMEMORY : 0;\n}\n\nstatic errno_t rt_heap_reallocate(rt_heap_t* h, void* *p, int64_t bytes,\n        bool zero) {\n    rt_swear(bytes > 0);\n    const DWORD flags = zero ? HEAP_ZERO_MEMORY : 0;\n    void* a = *p == null ? // HeapReAlloc(..., null, bytes) may not work\n        HeapAlloc(rt_heap_or_process_heap(h), flags, (SIZE_T)bytes) :\n        HeapReAlloc(rt_heap_or_process_heap(h), flags, *p, (SIZE_T)bytes);\n    if (a != null) { *p = a; }\n    return a == null ? ERROR_OUTOFMEMORY : 0;\n}\n\nstatic void rt_heap_deallocate(rt_heap_t* h, void* a) {\n    rt_fatal_win32err(HeapFree(rt_heap_or_process_heap(h), 0, a));\n}\n\nstatic int64_t rt_heap_bytes(rt_heap_t* h, void* a) {\n    SIZE_T bytes = HeapSize(rt_heap_or_process_heap(h), 0, a);\n    rt_fatal_if(bytes == (SIZE_T)-1);\n    return (int64_t)bytes;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_heap_test(void) {\n    // TODO: allocate, reallocate deallocate, create, dispose\n    void*   a[1024]; // addresses\n    int32_t b[1024]; // bytes\n    uint32_t seed = 0x1;\n    for (int i = 0; i < 1024; i++) {\n        b[i] = (int32_t)(rt_num.random32(&seed) % 1024) + 1;\n        errno_t r = rt_heap.alloc(&a[i], b[i]);\n        rt_swear(r == 0);\n    }\n    for (int i = 0; i < 1024; i++) {\n        rt_heap.free(a[i]);\n    }\n    HeapCompact(rt_heap_or_process_heap(null), 0);\n    // \"There is no extended error information for HeapValidate;\n    //  do not call GetLastError.\"\n    rt_swear(HeapValidate(rt_heap_or_process_heap(null), 0, null));\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_heap_test(void) { }\n\n#endif\n\nrt_heap_if rt_heap = {\n    .alloc        = rt_heap_alloc,\n    .alloc_zero   = rt_heap_alloc_zero,\n    .realloc      = rt_heap_realloc,\n    .realloc_zero = rt_heap_realloc_zero,\n    .free         = rt_heap_free,\n    .create       = rt_heap_create,\n    .allocate     = rt_heap_allocate,\n    .reallocate   = rt_heap_reallocate,\n    .deallocate   = rt_heap_deallocate,\n    .bytes        = rt_heap_bytes,\n    .dispose      = rt_heap_dispose,\n    .test         = rt_heap_test\n};\n\n// _______________________________ rt_loader.c ________________________________\n\n// This is oversimplified Win32 version completely ignoring mode.\n\n// I bit more Posix compliant version is here:\n// https://github.com/dlfcn-win32/dlfcn-win32/blob/master/src/dlfcn.c\n// POSIX says that if the value of file is NULL, a handle on a global\n// symbol object must be provided. That object must be able to access\n// all symbols from the original program file, and any objects loaded\n// with the RTLD_GLOBAL flag.\n// The return value from GetModuleHandle( ) allows us to retrieve\n// symbols only from the original program file. EnumProcessModules() is\n// used to access symbols from other libraries. For objects loaded\n// with the RTLD_LOCAL flag, we create our own list later on. They are\n// excluded from EnumProcessModules() iteration.\n\nstatic void* rt_loader_all;\n\nstatic void* rt_loader_sym_all(const char* name) {\n    void* sym = null;\n    DWORD bytes = 0;\n    rt_fatal_win32err(EnumProcessModules(GetCurrentProcess(),\n                                         null, 0, &bytes));\n    rt_assert(bytes % sizeof(HMODULE) == 0);\n    rt_assert(bytes / sizeof(HMODULE) < 1024); // OK to allocate 8KB on stack\n    HMODULE* modules = null;\n    rt_fatal_if_error(rt_heap.allocate(null, (void**)&modules, bytes, false));\n    rt_fatal_win32err(EnumProcessModules(GetCurrentProcess(),\n                                         modules, bytes, &bytes));\n    const int32_t n = bytes / (int32_t)sizeof(HMODULE);\n    for (int32_t i = 0; i < n && sym != null; i++) {\n        sym = rt_loader.sym(modules[i], name);\n    }\n    if (sym == null) {\n        sym = rt_loader.sym(GetModuleHandleA(null), name);\n    }\n    rt_heap.deallocate(null, modules);\n    return sym;\n}\n\nstatic void* rt_loader_open(const char* filename, int32_t rt_unused(mode)) {\n    return filename == null ? &rt_loader_all : (void*)LoadLibraryA(filename);\n}\n\nstatic void* rt_loader_sym(void* handle, const char* name) {\n    return handle == &rt_loader_all ?\n            (void*)rt_loader_sym_all(name) :\n            (void*)GetProcAddress((HMODULE)handle, name);\n}\n\nstatic void rt_loader_close(void* handle) {\n    if (handle != &rt_loader_all) {\n        rt_fatal_win32err(FreeLibrary(handle));\n    }\n}\n\n#ifdef RT_TESTS\n\n// manually test exported function once and comment out because of\n// creating .lib out of each .exe is annoying\n\n#undef RT_LOADER_TEST_EXPORTED_FUNCTION\n\n#ifdef RT_LOADER_TEST_EXPORTED_FUNCTION\n\n\nstatic int32_t rt_loader_test_calls_count;\n\nrt_export void rt_loader_test_exported_function(void);\n\nvoid rt_loader_test_exported_function(void) { rt_loader_test_calls_count++; }\n\n#endif\n\nstatic void rt_loader_test(void) {\n    void* global = rt_loader.open(null, rt_loader.local);\n    rt_loader.close(global);\n    // NtQueryTimerResolution - http://undocumented.ntinternals.net/\n    typedef long (__stdcall *query_timer_resolution_t)(\n        long* minimum_resolution,\n        long* maximum_resolution,\n        long* current_resolution);\n    void* nt_dll = rt_loader.open(\"ntdll\", rt_loader.local);\n    query_timer_resolution_t query_timer_resolution =\n        (query_timer_resolution_t)rt_loader.sym(nt_dll, \"NtQueryTimerResolution\");\n    // in 100ns = 0.1us units\n    long min_resolution = 0;\n    long max_resolution = 0; // lowest possible delay between timer events\n    long cur_resolution = 0;\n    rt_fatal_if(query_timer_resolution(\n        &min_resolution, &max_resolution, &cur_resolution) != 0);\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f millisecond\",\n//          min_resolution / 10.0 / 1000.0,\n//          max_resolution / 10.0 / 1000.0,\n//          cur_resolution / 10.0 / 1000.0);\n//      // Interesting observation cur_resolution sometimes 15.625ms or 1.0ms\n//  }\n    rt_loader.close(nt_dll);\n#ifdef RT_LOADER_TEST_EXPORTED_FUNCTION\n    rt_loader_test_calls_count = 0;\n    rt_loader_test_exported_function(); // to make sure it is linked in\n    rt_swear(rt_loader_test_calls_count == 1);\n    typedef void (*foo_t)(void);\n    foo_t foo = (foo_t)rt_loader.sym(global, \"rt_loader_test_exported_function\");\n    foo();\n    rt_swear(rt_loader_test_calls_count == 2);\n#endif\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_loader_test(void) {}\n\n#endif\n\nenum {\n    rt_loader_local  = 0,       // RTLD_LOCAL  All symbols are not made available for relocation processing by other modules.\n    rt_loader_lazy   = 1,       // RTLD_LAZY   Relocations are performed at an implementation-dependent time.\n    rt_loader_now    = 2,       // RTLD_NOW    Relocations are performed when the object is loaded.\n    rt_loader_global = 0x00100, // RTLD_GLOBAL All symbols are available for relocation processing of other modules.\n};\n\nrt_loader_if rt_loader = {\n    .local  = rt_loader_local,\n    .lazy   = rt_loader_lazy,\n    .now    = rt_loader_now,\n    .global = rt_loader_global,\n    .open   = rt_loader_open,\n    .sym    = rt_loader_sym,\n    .close  = rt_loader_close,\n    .test   = rt_loader_test\n};\n\n// _________________________________ rt_mem.c _________________________________\n\nstatic errno_t rt_mem_map_view_of_file(HANDLE file,\n        void* *data, int64_t *bytes, bool rw) {\n    errno_t r = 0;\n    void* address = null;\n    HANDLE mapping = CreateFileMapping(file, null,\n        rw ? PAGE_READWRITE : PAGE_READONLY,\n        (uint32_t)(*bytes >> 32), (uint32_t)*bytes, null);\n    if (mapping == null) {\n        r = rt_core.err();\n    } else {\n        DWORD access = rw ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ;\n        address = MapViewOfFile(mapping, access, 0, 0, (SIZE_T)*bytes);\n        if (address == null) { r = rt_core.err(); }\n        rt_win32_close_handle(mapping);\n    }\n    if (r == 0) {\n        *data = address;\n    } else {\n        *data = null;\n        *bytes = 0;\n    }\n    return r;\n}\n\n// see: https://learn.microsoft.com/en-us/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--\n\nstatic errno_t rt_mem_set_token_privilege(void* token,\n            const char* name, bool e) {\n    TOKEN_PRIVILEGES tp = { .PrivilegeCount = 1 };\n    tp.Privileges[0].Attributes = e ? SE_PRIVILEGE_ENABLED : 0;\n    rt_fatal_win32err(LookupPrivilegeValueA(null, name, &tp.Privileges[0].Luid));\n    return rt_b2e(AdjustTokenPrivileges(token, false, &tp,\n               sizeof(TOKEN_PRIVILEGES), null, null));\n}\n\nstatic errno_t rt_mem_adjust_process_privilege_manage_volume_name(void) {\n    // see: https://devblogs.microsoft.com/oldnewthing/20160603-00/?p=93565\n    const uint32_t access = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;\n    const HANDLE process = GetCurrentProcess();\n    HANDLE token = null;\n    errno_t r = rt_b2e(OpenProcessToken(process, access, &token));\n    if (r == 0) {\n        const char* se_manage_volume_name = \"SeManageVolumePrivilege\";\n        r = rt_mem_set_token_privilege(token, se_manage_volume_name, true);\n        rt_win32_close_handle(token);\n    }\n    return r;\n}\n\nstatic errno_t rt_mem_map_file(const char* filename, void* *data,\n        int64_t *bytes, bool rw) {\n    if (rw) { // for SetFileValidData() call:\n        (void)rt_mem_adjust_process_privilege_manage_volume_name();\n    }\n    errno_t r = 0;\n    const DWORD access = GENERIC_READ | (rw ? GENERIC_WRITE : 0);\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    const DWORD disposition = rw ? OPEN_ALWAYS : OPEN_EXISTING;\n    const DWORD flags = FILE_ATTRIBUTE_NORMAL;\n    HANDLE file = CreateFileA(filename, access, share, null, disposition,\n                              flags, null);\n    if (file == INVALID_HANDLE_VALUE) {\n        r = rt_core.err();\n    } else {\n        LARGE_INTEGER eof = { .QuadPart = 0 };\n        rt_fatal_win32err(GetFileSizeEx(file, &eof));\n        if (rw && *bytes > eof.QuadPart) { // increase file size\n            const LARGE_INTEGER size = { .QuadPart = *bytes };\n            r = r != 0 ? r : (rt_b2e(SetFilePointerEx(file, size, null, FILE_BEGIN)));\n            r = r != 0 ? r : (rt_b2e(SetEndOfFile(file)));\n            // the following not guaranteed to work but helps with sparse files\n            r = r != 0 ? r : (rt_b2e(SetFileValidData(file, *bytes)));\n            // SetFileValidData() only works for Admin (verified) or System accounts\n            if (r == ERROR_PRIVILEGE_NOT_HELD) { r = 0; } // ignore\n            // SetFileValidData() is also semi-security hole because it allows to read\n            // previously not zeroed disk content of other files\n            const LARGE_INTEGER zero = { .QuadPart = 0 }; // rewind stream:\n            r = r != 0 ? r : (rt_b2e(SetFilePointerEx(file, zero, null, FILE_BEGIN)));\n        } else {\n            *bytes = eof.QuadPart;\n        }\n        r = r != 0 ? r : rt_mem_map_view_of_file(file, data, bytes, rw);\n        rt_win32_close_handle(file);\n    }\n    return r;\n}\n\nstatic errno_t rt_mem_map_ro(const char* filename, void* *data, int64_t *bytes) {\n    return rt_mem_map_file(filename, data, bytes, false);\n}\n\nstatic errno_t rt_mem_map_rw(const char* filename, void* *data, int64_t *bytes) {\n    return rt_mem_map_file(filename, data, bytes, true);\n}\n\nstatic void rt_mem_unmap(void* data, int64_t bytes) {\n    rt_assert(data != null && bytes > 0);\n    (void)bytes; /* unused only need for posix version */\n    if (data != null && bytes > 0) {\n        rt_fatal_win32err(UnmapViewOfFile(data));\n    }\n}\n\nstatic errno_t rt_mem_map_resource(const char* label, void* *data, int64_t *bytes) {\n    HRSRC res = FindResourceA(null, label, (const char*)RT_RCDATA);\n    // \"LockResource does not actually lock memory; it is just used to\n    // obtain a pointer to the memory containing the resource data.\n    // The name of the function comes from versions prior to Windows XP,\n    // when it was used to lock a global memory block allocated by LoadResource.\"\n    if (res != null) { *bytes = SizeofResource(null, res); }\n    HGLOBAL g = res != null ? LoadResource(null, res) : null;\n    *data = g != null ? LockResource(g) : null;\n    return *data != null ? 0 : rt_core.err();\n}\n\nstatic int32_t rt_mem_page_size(void) {\n    static SYSTEM_INFO system_info;\n    if (system_info.dwPageSize == 0) {\n        GetSystemInfo(&system_info);\n    }\n    return (int32_t)system_info.dwPageSize;\n}\n\nstatic int rt_mem_large_page_size(void) {\n    static SIZE_T large_page_minimum = 0;\n    if (large_page_minimum == 0) {\n        large_page_minimum = GetLargePageMinimum();\n    }\n    return (int32_t)large_page_minimum;\n}\n\nstatic void* rt_mem_allocate(int64_t bytes_multiple_of_page_size) {\n    rt_assert(bytes_multiple_of_page_size > 0);\n    SIZE_T bytes = (SIZE_T)bytes_multiple_of_page_size;\n    SIZE_T page_size = (SIZE_T)rt_mem_page_size();\n    rt_assert(bytes % page_size == 0);\n    errno_t r = 0;\n    void* a = null;\n    if (bytes_multiple_of_page_size < 0 || bytes % page_size != 0) {\n        SetLastError(ERROR_INVALID_PARAMETER);\n        r = EINVAL;\n    } else {\n        const DWORD type = MEM_COMMIT | MEM_RESERVE;\n        const DWORD physical = type | MEM_PHYSICAL;\n        a = VirtualAlloc(null, bytes, physical, PAGE_READWRITE);\n        if (a == null) {\n            a = VirtualAlloc(null, bytes, type, PAGE_READWRITE);\n        }\n        if (a == null) {\n            r = rt_core.err();\n            if (r != 0) {\n                rt_println(\"VirtualAlloc(%lld) failed %s\", bytes, rt_strerr(r));\n            }\n        } else {\n            r = VirtualLock(a, bytes) ? 0 : rt_core.err();\n            if (r == ERROR_WORKING_SET_QUOTA) {\n                // The default size is 345 pages (for example,\n                // this is 1,413,120 bytes on systems with a 4K page size).\n                SIZE_T min_mem = 0, max_mem = 0;\n                r = rt_b2e(GetProcessWorkingSetSize(GetCurrentProcess(), &min_mem, &max_mem));\n                if (r != 0) {\n                    rt_println(\"GetProcessWorkingSetSize() failed %s\", rt_strerr(r));\n                } else {\n                    max_mem =  max_mem + bytes * 2LL;\n                    max_mem = (max_mem + page_size - 1) / page_size * page_size +\n                               page_size * 16;\n                    if (min_mem < max_mem) { min_mem = max_mem; }\n                    r = rt_b2e(SetProcessWorkingSetSize(GetCurrentProcess(),\n                            min_mem, max_mem));\n                    if (r != 0) {\n                        rt_println(\"SetProcessWorkingSetSize(%lld, %lld) failed %s\",\n                            (uint64_t)min_mem, (uint64_t)max_mem, rt_strerr(r));\n                    } else {\n                        r = rt_b2e(VirtualLock(a, bytes));\n                    }\n                }\n            }\n            if (r != 0) {\n                rt_println(\"VirtualLock(%lld) failed %s\", bytes, rt_strerr(r));\n            }\n        }\n    }\n    if (r != 0) {\n        rt_println(\"mem_alloc_pages(%lld) failed %s\", bytes, rt_strerr(r));\n        rt_assert(a == null);\n    }\n    return a;\n}\n\nstatic void rt_mem_deallocate(void* a, int64_t bytes_multiple_of_page_size) {\n    rt_assert(bytes_multiple_of_page_size > 0);\n    SIZE_T bytes = (SIZE_T)bytes_multiple_of_page_size;\n    errno_t r = 0;\n    SIZE_T page_size = (SIZE_T)rt_mem_page_size();\n    if (bytes_multiple_of_page_size < 0 || bytes % page_size != 0) {\n        r = EINVAL;\n        rt_println(\"failed %s\", rt_strerr(r));\n    } else {\n        if (a != null) {\n            // in case it was successfully locked\n            r = rt_b2e(VirtualUnlock(a, bytes));\n            if (r != 0) {\n                rt_println(\"VirtualUnlock() failed %s\", rt_strerr(r));\n            }\n            // If the \"dwFreeType\" parameter is MEM_RELEASE, \"dwSize\" parameter\n            // must be the base address returned by the VirtualAlloc function when\n            // the region of pages is reserved.\n            r = rt_b2e(VirtualFree(a, 0, MEM_RELEASE));\n            if (r != 0) { rt_println(\"VirtuaFree() failed %s\", rt_strerr(r)); }\n        }\n    }\n}\n\nstatic void rt_mem_test(void) {\n    #ifdef RT_TESTS\n    rt_swear(rt_args.c > 0);\n    void* data = null;\n    int64_t bytes = 0;\n    rt_swear(rt_mem.map_ro(rt_args.v[0], &data, &bytes) == 0);\n    rt_swear(data != null && bytes != 0);\n    rt_mem.unmap(data, bytes);\n    // TODO: page_size large_page_size allocate deallocate\n    // TODO: test heap functions\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_mem_if rt_mem = {\n    .map_ro          = rt_mem_map_ro,\n    .map_rw          = rt_mem_map_rw,\n    .unmap           = rt_mem_unmap,\n    .map_resource    = rt_mem_map_resource,\n    .page_size       = rt_mem_page_size,\n    .large_page_size = rt_mem_large_page_size,\n    .allocate        = rt_mem_allocate,\n    .deallocate      = rt_mem_deallocate,\n    .test            = rt_mem_test\n};\n\n// _________________________________ rt_nls.c _________________________________\n\n// Simplistic Win32 implementation of national language support.\n// Windows NLS family of functions is very complicated and has\n// difficult history of LANGID vs LCID etc... See:\n// ResolveLocaleName()\n// GetThreadLocale()\n// SetThreadLocale()\n// GetUserDefaultLocaleName()\n// WM_SETTINGCHANGE lParam=\"intl\"\n// and many others...\n\nenum {\n    rt_nls_str_count_max = 1024,\n    rt_nls_str_mem_max = 64 * rt_nls_str_count_max\n};\n\nstatic char  rt_nls_strings_memory[rt_nls_str_mem_max]; // increase if overflows\nstatic char* rt_nls_strings_free = rt_nls_strings_memory;\n\nstatic int32_t rt_nls_strings_count;\n\nstatic const char* rt_nls_ls[rt_nls_str_count_max]; // localized strings\nstatic const char* rt_nls_ns[rt_nls_str_count_max]; // neutral language strings\n\nstatic uint16_t* rt_nls_load_string(int32_t strid, LANGID lang_id) {\n    rt_assert(0 <= strid && strid < rt_countof(rt_nls_ns));\n    uint16_t* r = null;\n    int32_t block = strid / 16 + 1;\n    int32_t index  = strid % 16;\n    HRSRC res = FindResourceExW(((HMODULE)null), RT_STRING,\n        MAKEINTRESOURCEW(block), lang_id);\n//  rt_println(\"FindResourceExA(block=%d lang_id=%04X)=%p\", block, lang_id, res);\n    uint8_t* memory = res == null ? null : (uint8_t*)LoadResource(null, res);\n    uint16_t* ws = memory == null ? null : (uint16_t*)LockResource(memory);\n//  rt_println(\"LockResource(block=%d lang_id=%04X)=%p\", block, lang_id, ws);\n    if (ws != null) {\n        for (int32_t i = 0; i < 16 && r == null; i++) {\n            if (ws[0] != 0) {\n                int32_t count = (int32_t)ws[0];  // String size in characters.\n                ws++;\n                rt_assert(ws[count - 1] == 0, \"use rc.exe /n command line option\");\n                if (i == index) { // the string has been found\n//                  rt_println(\"%04X found %s\", lang_id, utf16to8(ws));\n                    r = ws;\n                }\n                ws += count;\n            } else {\n                ws++;\n            }\n        }\n    }\n    return r;\n}\n\nstatic const char* rt_nls_save_string(uint16_t* utf16) {\n    const int32_t bytes = rt_str.utf8_bytes(utf16, -1);\n    rt_swear(bytes > 1);\n    char* s = rt_nls_strings_free;\n    uintptr_t left = (uintptr_t)rt_countof(rt_nls_strings_memory) -\n        (uintptr_t)(rt_nls_strings_free - rt_nls_strings_memory);\n    rt_fatal_if(left < (uintptr_t)bytes, \"string_memory[] overflow\");\n    rt_str.utf16to8(s, (int32_t)left, utf16, -1);\n    rt_assert((int32_t)strlen(s) == bytes - 1, \"utf16to8() does not truncate\");\n    rt_nls_strings_free += bytes;\n    return s;\n}\n\nstatic const char* rt_nls_localized_string(int32_t strid) {\n    rt_swear(0 < strid && strid < rt_countof(rt_nls_ns));\n    const char* s = null;\n    if (0 < strid && strid < rt_countof(rt_nls_ns)) {\n        if (rt_nls_ls[strid] != null) {\n            s = rt_nls_ls[strid];\n        } else {\n            LCID lc_id = GetThreadLocale();\n            LANGID lang_id = LANGIDFROMLCID(lc_id);\n            uint16_t* utf16 = rt_nls_load_string(strid, lang_id);\n            if (utf16 == null) { // try default dialect:\n                LANGID primary = PRIMARYLANGID(lang_id);\n                lang_id = MAKELANGID(primary, SUBLANG_NEUTRAL);\n                utf16 = rt_nls_load_string(strid, lang_id);\n            }\n            if (utf16 != null && utf16[0] != 0x0000) {\n                s = rt_nls_save_string(utf16);\n                rt_nls_ls[strid] = s;\n            }\n        }\n    }\n    return s;\n}\n\nstatic int32_t rt_nls_strid(const char* s) {\n    int32_t strid = -1;\n    for (int32_t i = 1; i < rt_nls_strings_count && strid == -1; i++) {\n        if (rt_nls_ns[i] != null && strcmp(s, rt_nls_ns[i]) == 0) {\n            strid = i;\n            rt_nls_localized_string(strid); // to save it, ignore result\n        }\n    }\n    return strid;\n}\n\nstatic const char* rt_nls_string(int32_t strid, const char* defau1t) {\n    const char* r = rt_nls_localized_string(strid);\n    return r == null ? defau1t : r;\n}\n\nstatic const char* rt_nls_str(const char* s) {\n    int32_t id = rt_nls_strid(s);\n    return id < 0 ? s : rt_nls_string(id, s);\n}\n\nstatic const char* rt_nls_locale(void) {\n    uint16_t utf16[LOCALE_NAME_MAX_LENGTH + 1];\n    LCID lc_id = GetThreadLocale();\n    int32_t n = LCIDToLocaleName(lc_id, utf16, rt_countof(utf16),\n        LOCALE_ALLOW_NEUTRAL_NAMES);\n    static char ln[LOCALE_NAME_MAX_LENGTH * 4 + 1];\n    ln[0] = 0;\n    if (n == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"LCIDToLocaleName(0x%04X) failed %s\", lc_id, rt_str.error(r));\n    } else {\n        rt_str.utf16to8(ln, rt_countof(ln), utf16, -1);\n    }\n    return ln;\n}\n\nstatic errno_t rt_nls_set_locale(const char* locale) {\n    errno_t r = 0;\n    uint16_t utf16[LOCALE_NAME_MAX_LENGTH + 1];\n    rt_str.utf8to16(utf16, rt_countof(utf16), locale, -1);\n    uint16_t rln[LOCALE_NAME_MAX_LENGTH + 1]; // resolved locale name\n    int32_t n = (int32_t)ResolveLocaleName(utf16, rln, (DWORD)rt_countof(rln));\n    if (n == 0) {\n        r = rt_core.err();\n        rt_println(\"ResolveLocaleName(\\\"%s\\\") failed %s\", locale, rt_str.error(r));\n    } else {\n        LCID lc_id = LocaleNameToLCID(rln, LOCALE_ALLOW_NEUTRAL_NAMES);\n        if (lc_id == 0) {\n            r = rt_core.err();\n            rt_println(\"LocaleNameToLCID(\\\"%s\\\") failed %s\", locale, rt_str.error(r));\n        } else {\n            rt_fatal_win32err(SetThreadLocale(lc_id));\n            memset((void*)rt_nls_ls, 0, sizeof(rt_nls_ls)); // start all over\n        }\n    }\n    return r;\n}\n\nstatic void rt_nls_init(void) {\n    static_assert(rt_countof(rt_nls_ns) % 16 == 0, \n                 \"rt_countof(ns) must be multiple of 16\");\n    LANGID lang_id = MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL);\n    for (int32_t strid = 0; strid < rt_countof(rt_nls_ns); strid += 16) {\n        int32_t block = strid / 16 + 1;\n        HRSRC res = FindResourceExW(((HMODULE)null), RT_STRING,\n            MAKEINTRESOURCEW(block), lang_id);\n        uint8_t* memory = res == null ? null : (uint8_t*)LoadResource(null, res);\n        uint16_t* ws = memory == null ? null : (uint16_t*)LockResource(memory);\n        if (ws == null) { break; }\n        for (int32_t i = 0; i < 16; i++) {\n            int32_t ix = strid + i;\n            uint16_t count = ws[0];\n            if (count > 0) {\n                ws++;\n                rt_fatal_if(ws[count - 1] != 0, \"use rc.exe /n\");\n                rt_nls_ns[ix] = rt_nls_save_string(ws);\n                rt_nls_strings_count = ix + 1;\n//              rt_println(\"ns[%d] := %d \\\"%s\\\"\", ix, strlen(rt_nls_ns[ix]), rt_nls_ns[ix]);\n                ws += count;\n            } else {\n                ws++;\n            }\n        }\n    }\n}\n\nrt_nls_if rt_nls = {\n    .init       = rt_nls_init,\n    .strid      = rt_nls_strid,\n    .str        = rt_nls_str,\n    .string     = rt_nls_string,\n    .locale     = rt_nls_locale,\n    .set_locale = rt_nls_set_locale,\n};\n// _________________________________ rt_num.c _________________________________\n\n#include <intrin.h>\n//#include <immintrin.h> // _tzcnt_u32\n\nstatic inline rt_num128_t rt_num_add128_inline(const rt_num128_t a, const rt_num128_t b) {\n    rt_num128_t r = a;\n    r.hi += b.hi;\n    r.lo += b.lo;\n    if (r.lo < b.lo) { r.hi++; } // carry\n    return r;\n}\n\nstatic inline rt_num128_t rt_num_sub128_inline(const rt_num128_t a, const rt_num128_t b) {\n    rt_num128_t r = a;\n    r.hi -= b.hi;\n    if (r.lo < b.lo) { r.hi--; } // borrow\n    r.lo -= b.lo;\n    return r;\n}\n\nstatic rt_num128_t rt_num_add128(const rt_num128_t a, const rt_num128_t b) {\n    return rt_num_add128_inline(a, b);\n}\n\nstatic rt_num128_t rt_num_sub128(const rt_num128_t a, const rt_num128_t b) {\n    return rt_num_sub128_inline(a, b);\n}\n\nstatic rt_num128_t rt_num_mul64x64(uint64_t a, uint64_t b) {\n    uint64_t a_lo = (uint32_t)a;\n    uint64_t a_hi = a >> 32;\n    uint64_t b_lo = (uint32_t)b;\n    uint64_t b_hi = b >> 32;\n    uint64_t low = a_lo * b_lo;\n    uint64_t cross1 = a_hi * b_lo;\n    uint64_t cross2 = a_lo * b_hi;\n    uint64_t high = a_hi * b_hi;\n    // this cannot overflow as (2^32-1)^2 + 2^32-1 < 2^64-1\n    cross1 += low >> 32;\n    // this one can overflow\n    cross1 += cross2;\n    // propagate the carry if any\n    high += ((uint64_t)(cross1 < cross2 != 0)) << 32;\n    high = high + (cross1 >> 32);\n    low = ((cross1 & 0xFFFFFFFF) << 32) + (low & 0xFFFFFFFF);\n    return (rt_num128_t){.lo = low, .hi = high };\n}\n\nstatic inline void rt_num_shift128_left_inline(rt_num128_t* n) {\n    const uint64_t top = (1ULL << 63);\n    n->hi = (n->hi << 1) | ((n->lo & top) ? 1 : 0);\n    n->lo = (n->lo << 1);\n}\n\nstatic inline void rt_num_shift128_right_inline(rt_num128_t* n) {\n    const uint64_t top = (1ULL << 63);\n    n->lo = (n->lo >> 1) | ((n->hi & 0x1) ? top : 0);\n    n->hi = (n->hi >> 1);\n}\n\nstatic inline bool rt_num_less128_inline(const rt_num128_t a, const rt_num128_t b) {\n    return a.hi < b.hi || (a.hi == b.hi && a.lo < b.lo);\n}\n\nstatic inline bool rt_num_uint128_high_bit(const rt_num128_t a) {\n    return (int64_t)a.hi < 0;\n}\n\nstatic uint64_t rt_num_muldiv128(uint64_t a, uint64_t b, uint64_t divisor) {\n    rt_swear(divisor > 0, \"divisor: %lld\", divisor);\n    rt_num128_t r = rt_num.mul64x64(a, b); // reminder: a * b\n    uint64_t q = 0; // quotient\n    if (r.hi >= divisor) {\n        q = UINT64_MAX; // overflow\n    } else {\n        int32_t  shift = 0;\n        rt_num128_t d = { .hi = 0, .lo = divisor };\n        while (!rt_num_uint128_high_bit(d) && rt_num_less128_inline(d, r)) {\n            rt_num_shift128_left_inline(&d);\n            shift++;\n        }\n        rt_assert(shift <= 64);\n        while (shift >= 0 && (d.hi != 0 || d.lo != 0)) {\n            if (!rt_num_less128_inline(r, d)) {\n                r = rt_num_sub128_inline(r, d);\n                rt_assert(shift < 64);\n                q |= (1ULL << shift);\n            }\n            rt_num_shift128_right_inline(&d);\n            shift--;\n        }\n    }\n    return q;\n}\n\nstatic uint32_t rt_num_gcd32(uint32_t u, uint32_t v) {\n    #pragma push_macro(\"rt_trailing_zeros\")\n    #ifdef _M_ARM64\n    #define rt_trailing_zeros(x) (_CountTrailingZeros(x))\n    #else\n    #define rt_trailing_zeros(x) ((int32_t)_tzcnt_u32(x))\n    #endif\n    if (u == 0) {\n        return v;\n    } else if (v == 0) {\n        return u;\n    }\n    uint32_t i = rt_trailing_zeros(u);  u >>= i;\n    uint32_t j = rt_trailing_zeros(v);  v >>= j;\n    uint32_t k = rt_min(i, j);\n    for (;;) {\n        rt_assert(u % 2 == 1, \"u = %d should be odd\", u);\n        rt_assert(v % 2 == 1, \"v = %d should be odd\", v);\n        if (u > v) { uint32_t swap = u; u = v; v = swap; }\n        v -= u;\n        if (v == 0) { return u << k; }\n        v >>= rt_trailing_zeros(v);\n    }\n    #pragma pop_macro(\"rt_trailing_zeros\")\n}\n\nstatic uint32_t rt_num_random32(uint32_t* state) {\n    // https://gist.github.com/tommyettinger/46a874533244883189143505d203312c\n    static rt_thread_local bool started; // first seed must be odd\n    if (!started) { started = true; *state |= 1; }\n    uint32_t z = (*state += 0x6D2B79F5UL);\n    z = (z ^ (z >> 15)) * (z | 1UL);\n    z ^= z + (z ^ (z >> 7)) * (z | 61UL);\n    return z ^ (z >> 14);\n}\n\nstatic uint64_t rt_num_random64(uint64_t *state) {\n    // https://gist.github.com/tommyettinger/e6d3e8816da79b45bfe582384c2fe14a\n    static rt_thread_local bool started; // first seed must be odd\n    if (!started) { started = true; *state |= 1; }\n\tconst uint64_t s = *state;\n\tconst uint64_t z = (s ^ s >> 25) * (*state += 0x6A5D39EAE12657AAULL);\n\treturn z ^ (z >> 22);\n}\n\n// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\n\nstatic uint32_t rt_num_hash32(const char *data, int64_t len) {\n    uint32_t hash  = 0x811c9dc5;  // FNV_offset_basis for 32-bit\n    uint32_t prime = 0x01000193; // FNV_prime for 32-bit\n    if (len > 0) {\n        for (int64_t i = 1; i < len; i++) {\n            hash ^= (uint32_t)data[i];\n            hash *= prime;\n        }\n    } else {\n        for (int64_t i = 0; data[i] != 0; i++) {\n            hash ^= (uint32_t)data[i];\n            hash *= prime;\n        }\n    }\n    return hash;\n}\n\nstatic uint64_t rt_num_hash64(const char *data, int64_t len) {\n    uint64_t hash  = 0xcbf29ce484222325; // FNV_offset_basis for 64-bit\n    uint64_t prime = 0x100000001b3;      // FNV_prime for 64-bit\n    if (len > 0) {\n        for (int64_t i = 0; i < len; i++) {\n            hash ^= (uint64_t)data[i];\n            hash *= prime;\n        }\n    } else {\n        for (int64_t i = 0; data[i] != 0; i++) {\n            hash ^= (uint64_t)data[i];\n            hash *= prime;\n        }\n    }\n    return hash;\n}\n\nstatic uint32_t ctz_2(uint32_t x) {\n    if (x == 0) return 32;\n    unsigned n = 0;\n    while ((x & 1) == 0) {\n        x >>= 1;\n        n++;\n    }\n    return n;\n}\n\nstatic void rt_num_test(void) {\n    #ifdef RT_TESTS\n    {\n        rt_swear(rt_num.gcd32(1000000000, 24000000) == 8000000);\n        // https://asecuritysite.com/encryption/nprimes?y=64\n        // https://www.rapidtables.com/convert/number/decimal-to-hex.html\n        uint64_t p = 15843490434539008357u; // prime\n        uint64_t q = 16304766625841520833u; // prime\n        // pq: 258324414073910997987910483408576601381\n        //     0xC25778F20853A9A1EC0C27C467C45D25\n        rt_num128_t pq = {.hi = 0xC25778F20853A9A1uLL,\n                       .lo = 0xEC0C27C467C45D25uLL };\n        rt_num128_t p_q = rt_num.mul64x64(p, q);\n        rt_swear(p_q.hi == pq.hi && pq.lo == pq.lo);\n        uint64_t p1 = rt_num.muldiv128(p, q, q);\n        uint64_t q1 = rt_num.muldiv128(p, q, p);\n        rt_swear(p1 == p);\n        rt_swear(q1 == q);\n    }\n    #ifdef DEBUG\n    enum { n = 100 };\n    #else\n    enum { n = 10000 };\n    #endif\n    uint64_t seed64 = 1;\n    for (int32_t i = 0; i < n; i++) {\n        uint64_t p = rt_num.random64(&seed64);\n        uint64_t q = rt_num.random64(&seed64);\n        uint64_t p1 = rt_num.muldiv128(p, q, q);\n        uint64_t q1 = rt_num.muldiv128(p, q, p);\n        rt_swear(p == p1, \"0%16llx (0%16llu) != 0%16llx (0%16llu)\", p, p1);\n        rt_swear(q == q1, \"0%16llx (0%16llu) != 0%16llx (0%16llu)\", p, p1);\n    }\n    uint32_t seed32 = 1;\n    for (int32_t i = 0; i < n; i++) {\n        uint64_t p = rt_num.random32(&seed32);\n        uint64_t q = rt_num.random32(&seed32);\n        uint64_t r = rt_num.muldiv128(p, q, 1);\n        rt_swear(r == p * q);\n        // division by the maximum uint64_t value:\n        r = rt_num.muldiv128(p, q, UINT64_MAX);\n        rt_swear(r == 0);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_num_if rt_num = {\n    .add128    = rt_num_add128,\n    .sub128    = rt_num_sub128,\n    .mul64x64  = rt_num_mul64x64,\n    .muldiv128 = rt_num_muldiv128,\n    .gcd32     = rt_num_gcd32,\n    .random32  = rt_num_random32,\n    .random64  = rt_num_random64,\n    .hash32    = rt_num_hash32,\n    .hash64    = rt_num_hash64,\n    .test      = rt_num_test\n};\n\n// ______________________________ rt_processes.c ______________________________\n\ntypedef struct rt_processes_pidof_lambda_s rt_processes_pidof_lambda_t;\n\ntypedef struct rt_processes_pidof_lambda_s {\n    bool (*each)(rt_processes_pidof_lambda_t* p, uint64_t pid); // returns true to continue\n    uint64_t* pids;\n    size_t size;  // pids[size]\n    size_t count; // number of valid pids in the pids\n    fp64_t timeout;\n    errno_t error;\n} rt_processes_pidof_lambda_t;\n\nstatic int32_t rt_processes_for_each_pidof(const char* pname, rt_processes_pidof_lambda_t* la) {\n    char stack[1024]; // avoid alloca()\n    int32_t n = rt_str.len(pname);\n    rt_fatal_if(n + 5 >= rt_countof(stack), \"name is too long: %s\", pname);\n    const char* name = pname;\n    // append \".exe\" if not present:\n    if (!rt_str.iends(pname, \".exe\")) {\n        int32_t k = (int32_t)strlen(pname) + 5;\n        char* exe = stack;\n        rt_str.format(exe, k, \"%s.exe\", pname);\n        name = exe;\n    }\n    const char* base = strrchr(name, '\\\\');\n    if (base != null) {\n        base++; // advance past \"\\\\\"\n    } else {\n        base = name;\n    }\n    uint16_t wn[1024];\n    rt_fatal_if(strlen(base) >= rt_countof(wn), \"name too long: %s\", base);\n    rt_str.utf8to16(wn, rt_countof(wn), base, -1);\n    size_t count = 0;\n    uint64_t pid = 0;\n    uint8_t* data = null;\n    ULONG bytes = 0;\n    errno_t r = NtQuerySystemInformation(SystemProcessInformation, data, 0, &bytes);\n    #pragma push_macro(\"STATUS_INFO_LENGTH_MISMATCH\")\n    #define STATUS_INFO_LENGTH_MISMATCH      0xC0000004\n    while (r == (errno_t)STATUS_INFO_LENGTH_MISMATCH) {\n        // bytes == 420768 on Windows 11 which may be a bit\n        // too much for stack alloca()\n        // add little extra if new process is spawned in between calls.\n        bytes += sizeof(SYSTEM_PROCESS_INFORMATION) * 32;\n        r = rt_heap.reallocate(null, (void**)&data, bytes, false);\n        if (r == 0) {\n            r = NtQuerySystemInformation(SystemProcessInformation, data, bytes, &bytes);\n        } else {\n            rt_assert(r == (errno_t)ERROR_NOT_ENOUGH_MEMORY);\n        }\n    }\n    #pragma pop_macro(\"STATUS_INFO_LENGTH_MISMATCH\")\n    if (r == 0 && data != null) {\n        SYSTEM_PROCESS_INFORMATION* proc = (SYSTEM_PROCESS_INFORMATION*)data;\n        while (proc != null) {\n            uint16_t* img = proc->ImageName.Buffer; // last name only, not a pathname!\n            bool match = img != null && wcsicmp(img, wn) == 0;\n            if (match) {\n                pid = (uint64_t)proc->UniqueProcessId; // HANDLE .UniqueProcessId\n                if (base != name) {\n                    char path[rt_files_max_path];\n                    match = rt_processes.nameof(pid, path, rt_countof(path)) == 0 &&\n                            rt_str.iends(path, name);\n//                  rt_println(\"\\\"%s\\\" -> \\\"%s\\\" match: %d\", name, path, match);\n                }\n            }\n            if (match) {\n                if (la != null && count < la->size && la->pids != null) {\n                    la->pids[count] = pid;\n                }\n                count++;\n                if (la != null && la->each != null && !la->each(la, pid)) {\n                    break;\n                }\n            }\n            proc = proc->NextEntryOffset != 0 ? (SYSTEM_PROCESS_INFORMATION*)\n                ((uint8_t*)proc + proc->NextEntryOffset) : null;\n        }\n    }\n    if (data != null) { rt_heap.deallocate(null, data); }\n    rt_assert(count <= (uint64_t)INT32_MAX);\n    return (int32_t)count;\n}\n\nstatic errno_t rt_processes_nameof(uint64_t pid, char* name, int32_t count) {\n    rt_assert(name != null && count > 0);\n    errno_t r = 0;\n    name[0] = 0;\n    HANDLE p = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);\n    if (p != null) {\n        r = rt_b2e(GetModuleFileNameExA(p, null, name, count));\n        name[count - 1] = 0; // ensure zero termination\n        rt_win32_close_handle(p);\n    } else {\n        r = ERROR_NOT_FOUND;\n    }\n    return r;\n}\n\nstatic bool rt_processes_present(uint64_t pid) {\n    void* h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, (DWORD)pid);\n    bool b = h != null;\n    if (h != null) { rt_win32_close_handle(h); }\n    return b;\n}\n\nstatic bool rt_processes_first_pid(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    lambda->pids[0] = pid;\n    return false;\n}\n\nstatic uint64_t rt_processes_pid(const char* pname) {\n    uint64_t first[1] = {0};\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_first_pid,\n        .pids = first,\n        .size  = 1,\n        .count = 0,\n        .timeout = 0,\n        .error = 0\n    };\n    rt_processes_for_each_pidof(pname, &lambda);\n    return first[0];\n}\n\nstatic bool rt_processes_store_pid(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    if (lambda->pids != null && lambda->count < lambda->size) {\n        lambda->pids[lambda->count++] = pid;\n    }\n    return true; // always - need to count all\n}\n\nstatic errno_t rt_processes_pids(const char* pname, uint64_t* pids/*[size]*/,\n        int32_t size, int32_t *count) {\n    *count = 0;\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_store_pid,\n        .pids = pids,\n        .size = (size_t)size,\n        .count = 0,\n        .timeout = 0,\n        .error = 0\n    };\n    *count = rt_processes_for_each_pidof(pname, &lambda);\n    return (int32_t)lambda.count == *count ? 0 : ERROR_MORE_DATA;\n}\n\nstatic errno_t rt_processes_kill(uint64_t pid, fp64_t timeout) {\n    DWORD milliseconds = timeout < 0 ? INFINITE : (DWORD)(timeout * 1000);\n    enum { access = PROCESS_QUERY_LIMITED_INFORMATION |\n                    PROCESS_TERMINATE | SYNCHRONIZE };\n    rt_assert((DWORD)pid == pid); // Windows... HANDLE vs DWORD in different APIs\n    errno_t r = ERROR_NOT_FOUND;\n    HANDLE h = OpenProcess(access, 0, (DWORD)pid);\n    if (h != null) {\n        char path[rt_files_max_path];\n        path[0] = 0;\n        r = rt_b2e(TerminateProcess(h, ERROR_PROCESS_ABORTED));\n        if (r == 0) {\n            DWORD ix = WaitForSingleObject(h, milliseconds);\n            r = rt_wait_ix2e(ix);\n        } else {\n            DWORD bytes = rt_countof(path);\n            errno_t rq = rt_b2e(QueryFullProcessImageNameA(h, 0, path, &bytes));\n            if (rq != 0) {\n                rt_println(\"QueryFullProcessImageNameA(pid=%d, h=%p) \"\n                        \"failed %s\", pid, h, rt_strerr(rq));\n            }\n        }\n        rt_win32_close_handle(h);\n        if (r == ERROR_ACCESS_DENIED) { // special case\n            rt_thread.sleep_for(0.015); // need to wait a bit\n            HANDLE retry = OpenProcess(access, 0, (DWORD)pid);\n            // process may have died before we have chance to terminate it:\n            if (retry == null) {\n                rt_println(\"TerminateProcess(pid=%d, h=%p, im=%s) \"\n                        \"failed but zombie died after: %s\",\n                        pid, h, path, rt_strerr(r));\n                r = 0;\n            } else {\n                rt_win32_close_handle(retry);\n            }\n        }\n        if (r != 0) {\n            rt_println(\"TerminateProcess(pid=%d, h=%p, im=%s) failed %s\",\n                pid, h, path, rt_strerr(r));\n        }\n    }\n    if (r != 0) { errno = r; }\n    return r;\n}\n\nstatic bool rt_processes_kill_one(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    errno_t r = rt_processes_kill(pid, lambda->timeout);\n    if (r != 0) { lambda->error = r; }\n    return true; // keep going\n}\n\nstatic errno_t rt_processes_kill_all(const char* name, fp64_t timeout) {\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_kill_one,\n        .pids = null,\n        .size  = 0,\n        .count = 0,\n        .timeout = timeout,\n        .error = 0\n    };\n    int32_t c = rt_processes_for_each_pidof(name, &lambda);\n    return c == 0 ? ERROR_NOT_FOUND : lambda.error;\n}\n\nstatic bool rt_processes_is_elevated(void) { // Is process running as Admin / System ?\n    BOOL elevated = false;\n    PSID administrators_group = null;\n    // Allocate and initialize a SID of the administrators group.\n    SID_IDENTIFIER_AUTHORITY administrators_group_authority = SECURITY_NT_AUTHORITY;\n    errno_t r = rt_b2e(AllocateAndInitializeSid(&administrators_group_authority, 2,\n                SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,\n                0, 0, 0, 0, 0, 0, &administrators_group));\n    if (r != 0) {\n        rt_println(\"AllocateAndInitializeSid() failed %s\", rt_strerr(r));\n    }\n    PSID system_ops = null;\n    SID_IDENTIFIER_AUTHORITY system_ops_authority = SECURITY_NT_AUTHORITY;\n    r = rt_b2e(AllocateAndInitializeSid(&system_ops_authority, 2,\n            SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_SYSTEM_OPS,\n            0, 0, 0, 0, 0, 0, &system_ops));\n    if (r != 0) {\n        rt_println(\"AllocateAndInitializeSid() failed %s\", rt_strerr(r));\n    }\n    if (administrators_group != null) {\n        r = rt_b2e(CheckTokenMembership(null, administrators_group, &elevated));\n    }\n    if (system_ops != null && !elevated) {\n        r = rt_b2e(CheckTokenMembership(null, administrators_group, &elevated));\n    }\n    if (administrators_group != null) { FreeSid(administrators_group); }\n    if (system_ops != null) { FreeSid(system_ops); }\n    if (r != 0) {\n        rt_println(\"failed %s\", rt_strerr(r));\n    }\n    return elevated;\n}\n\nstatic errno_t rt_processes_restart_elevated(void) {\n    errno_t r = 0;\n    if (!rt_processes.is_elevated()) {\n        const char* path = rt_processes.name();\n        SHELLEXECUTEINFOA sei = { sizeof(sei) };\n        sei.lpVerb = \"runas\";\n        sei.lpFile = path;\n        sei.hwnd = null;\n        sei.nShow = SW_NORMAL;\n        r = rt_b2e(ShellExecuteExA(&sei));\n        if (r == ERROR_CANCELLED) {\n            rt_println(\"The user unable or refused to allow privileges elevation\");\n        } else if (r == 0) {\n            rt_core.exit(0); // second copy of the app is running now\n        }\n    }\n    return r;\n}\n\nstatic void rt_processes_close_pipes(STARTUPINFOA* si,\n        HANDLE *read_out,\n        HANDLE *read_err,\n        HANDLE *write_in) {\n    if (si->hStdOutput != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdOutput); }\n    if (si->hStdError  != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdError);  }\n    if (si->hStdInput  != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdInput);  }\n    if (*read_out != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*read_out); }\n    if (*read_err != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*read_err); }\n    if (*write_in != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*write_in); }\n}\n\nstatic errno_t rt_processes_child_read(rt_stream_if* out, HANDLE pipe) {\n    char data[32 * 1024]; // Temporary buffer for reading\n    DWORD available = 0;\n    errno_t r = rt_b2e(PeekNamedPipe(pipe, null, sizeof(data), null,\n                                 &available, null));\n    if (r != 0) {\n        if (r != ERROR_BROKEN_PIPE) { // unexpected!\n//          rt_println(\"PeekNamedPipe() failed %s\", rt_strerr(r));\n        }\n        // process has exited and closed the pipe\n        rt_assert(r == ERROR_BROKEN_PIPE);\n    } else if (available > 0) {\n        DWORD bytes_read = 0;\n        r = rt_b2e(ReadFile(pipe, data, sizeof(data), &bytes_read, null));\n//      rt_println(\"r: %d bytes_read: %d\", r, bytes_read);\n        if (out != null) {\n            if (r == 0) {\n                r = out->write(out, data, bytes_read, null);\n            }\n        } else {\n            // no one interested - drop on the floor\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_child_write(rt_stream_if* in, HANDLE pipe) {\n    errno_t r = 0;\n    if (in != null) {\n        uint8_t  memory[32 * 1024]; // Temporary buffer for reading\n        uint8_t* data = memory;\n        int64_t bytes_read = 0;\n        in->read(in, data, sizeof(data), &bytes_read);\n        while (r == 0 && bytes_read > 0) {\n            DWORD bytes_written = 0;\n            r = rt_b2e(WriteFile(pipe, data, (DWORD)bytes_read,\n                             &bytes_written, null));\n            rt_println(\"r: %d bytes_written: %d\", r, bytes_written);\n            rt_assert((int32_t)bytes_written <= bytes_read);\n            data += bytes_written;\n            bytes_read -= bytes_written;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_run(rt_processes_child_t* child) {\n    const fp64_t deadline = rt_clock.seconds() + child->timeout;\n    errno_t r = 0;\n    STARTUPINFOA si = {\n        .cb = sizeof(STARTUPINFOA),\n        .hStdInput  = INVALID_HANDLE_VALUE,\n        .hStdOutput = INVALID_HANDLE_VALUE,\n        .hStdError  = INVALID_HANDLE_VALUE,\n        .dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,\n        .wShowWindow = SW_HIDE\n    };\n    SECURITY_ATTRIBUTES sa = { sizeof(sa), null, true };  // Inheritable handles\n    PROCESS_INFORMATION pi = {0};\n    HANDLE read_out = INVALID_HANDLE_VALUE;\n    HANDLE read_err = INVALID_HANDLE_VALUE;\n    HANDLE write_in = INVALID_HANDLE_VALUE;\n    errno_t ro = rt_b2e(CreatePipe(&read_out, &si.hStdOutput, &sa, 0));\n    errno_t re = rt_b2e(CreatePipe(&read_err, &si.hStdError,  &sa, 0));\n    errno_t ri = rt_b2e(CreatePipe(&si.hStdInput, &write_in,  &sa, 0));\n    if (ro != 0 || re != 0 || ri != 0) {\n        rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        if (ro != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(ro)); r = ro; }\n        if (re != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(re)); r = re; }\n        if (ri != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(ri)); r = ri; }\n    }\n    if (r == 0) {\n        r = rt_b2e(CreateProcessA(null, rt_str.drop_const(child->command),\n                null, null, true, CREATE_NO_WINDOW, null, null, &si, &pi));\n        if (r != 0) {\n            rt_println(\"CreateProcess() failed %s\", rt_strerr(r));\n            rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        }\n    }\n    if (r == 0) {\n        // not relevant: stdout can be written in other threads\n        rt_win32_close_handle(pi.hThread);\n        pi.hThread = null;\n        // need to close si.hStdO* handles on caller side so,\n        // when the process closes handles of the pipes, EOF happens\n        // on caller side with io result ERROR_BROKEN_PIPE\n        // indicating no more data can be read or written\n        rt_win32_close_handle(si.hStdOutput);\n        rt_win32_close_handle(si.hStdError);\n        rt_win32_close_handle(si.hStdInput);\n        si.hStdOutput = INVALID_HANDLE_VALUE;\n        si.hStdError  = INVALID_HANDLE_VALUE;\n        si.hStdInput  = INVALID_HANDLE_VALUE;\n        bool done = false;\n        while (!done && r == 0) {\n            if (child->timeout > 0 && rt_clock.seconds() > deadline) {\n                r = rt_b2e(TerminateProcess(pi.hProcess, ERROR_SEM_TIMEOUT));\n                if (r != 0) {\n                    rt_println(\"TerminateProcess() failed %s\", rt_strerr(r));\n                } else {\n                    done = true;\n                }\n            }\n            if (r == 0) { r = rt_processes_child_write(child->in, write_in); }\n            if (r == 0) { r = rt_processes_child_read(child->out, read_out); }\n            if (r == 0) { r = rt_processes_child_read(child->err, read_err); }\n            if (!done) {\n                DWORD ix = WaitForSingleObject(pi.hProcess, 0);\n                // ix == 0 means process has exited (or terminated)\n                // r == ERROR_BROKEN_PIPE process closed one of the handles\n                done = ix == WAIT_OBJECT_0 || r == ERROR_BROKEN_PIPE;\n            }\n            // to avoid tight loop 100% cpu utilization:\n            if (!done) { rt_thread.yield(); }\n        }\n        // broken pipe actually signifies EOF on the pipe\n        if (r == ERROR_BROKEN_PIPE) { r = 0; } // not an error\n//      if (r != 0) { rt_println(\"pipe loop failed %s\", rt_strerr(r));}\n        DWORD xc = 0;\n        errno_t rx = rt_b2e(GetExitCodeProcess(pi.hProcess, &xc));\n        if (rx == 0) {\n            child->exit_code = xc;\n        } else {\n            rt_println(\"GetExitCodeProcess() failed %s\", rt_strerr(rx));\n            if (r != 0) { r = rx; } // report earliest error\n        }\n        rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        // expected never to fail\n        rt_win32_close_handle(pi.hProcess);\n    }\n    return r;\n}\n\ntypedef struct {\n    rt_stream_if stream;\n    rt_stream_if* output;\n    errno_t error;\n} rt_processes_io_merge_out_and_err_if;\n\nstatic errno_t rt_processes_merge_write(rt_stream_if* stream, const void* data,\n        int64_t bytes, int64_t* transferred) {\n    if (transferred != null) { *transferred = 0; }\n    rt_processes_io_merge_out_and_err_if* s =\n        (rt_processes_io_merge_out_and_err_if*)stream;\n    if (s->output != null && bytes > 0) {\n        s->error = s->output->write(s->output, data, bytes, transferred);\n    }\n    return s->error;\n}\n\nstatic errno_t rt_processes_open(const char* command, int32_t *exit_code,\n        rt_stream_if* output,  fp64_t timeout) {\n    rt_not_null(output);\n    rt_processes_io_merge_out_and_err_if merge_out_and_err = {\n        .stream ={ .write = rt_processes_merge_write },\n        .output = output,\n        .error = 0\n    };\n    rt_processes_child_t child = {\n        .command = command,\n        .in = null,\n        .out = &merge_out_and_err.stream,\n        .err = &merge_out_and_err.stream,\n        .exit_code = 0,\n        .timeout = timeout\n    };\n    errno_t r = rt_processes.run(&child);\n    if (exit_code != null) { *exit_code = (int32_t)child.exit_code; }\n    uint8_t zero = 0; // zero termination\n    merge_out_and_err.stream.write(&merge_out_and_err.stream, &zero, 1, null);\n    if (r == 0 && merge_out_and_err.error != 0) {\n        r = merge_out_and_err.error; // zero termination is not guaranteed\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_spawn(const char* command) {\n    errno_t r = 0;\n    STARTUPINFOA si = {\n        .cb = sizeof(STARTUPINFOA),\n        .dwFlags     = STARTF_USESHOWWINDOW\n                     | CREATE_NEW_PROCESS_GROUP\n                     | DETACHED_PROCESS,\n        .wShowWindow = SW_HIDE,\n        .hStdInput  = INVALID_HANDLE_VALUE,\n        .hStdOutput = INVALID_HANDLE_VALUE,\n        .hStdError  = INVALID_HANDLE_VALUE\n    };\n    const DWORD flags = CREATE_BREAKAWAY_FROM_JOB\n                | CREATE_NO_WINDOW\n                | CREATE_NEW_PROCESS_GROUP\n                | DETACHED_PROCESS;\n    PROCESS_INFORMATION pi = { .hProcess = null, .hThread = null };\n    r = rt_b2e(CreateProcessA(null, rt_str.drop_const(command), null, null,\n            /*bInheritHandles:*/false, flags, null, null, &si, &pi));\n    if (r == 0) { // Close handles immediately\n        rt_win32_close_handle(pi.hProcess);\n        rt_win32_close_handle(pi.hThread);\n    } else {\n        rt_println(\"CreateProcess() failed %s\", rt_strerr(r));\n    }\n    return r;\n}\n\nstatic const char* rt_processes_name(void) {\n    static char mn[rt_files_max_path];\n    if (mn[0] == 0) {\n        rt_fatal_win32err(GetModuleFileNameA(null, mn, rt_countof(mn)));\n    }\n    return mn;\n}\n\n#ifdef RT_TESTS\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                       \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                                   \\\n    }                                                           \\\n} while (0)\n\nstatic void rt_processes_test(void) {\n    #ifdef RT_TESTS // in alphabetical order\n    const char* names[] = { \"svchost\", \"RuntimeBroker\", \"conhost\" };\n    for (int32_t j = 0; j < rt_countof(names); j++) {\n        int32_t size  = 0;\n        int32_t count = 0;\n        uint64_t* pids = null;\n        errno_t r = rt_processes.pids(names[j], null, size, &count);\n        while (r == ERROR_MORE_DATA && count > 0) {\n            size = count * 2; // set of processes may change rapidly\n            r = rt_heap.reallocate(null, (void**)&pids,\n                                  (int64_t)sizeof(uint64_t) * (int64_t)size,\n                                  false);\n            if (r == 0) {\n                r = rt_processes.pids(names[j], pids, size, &count);\n            }\n        }\n        if (r == 0 && count > 0) {\n            for (int32_t i = 0; i < count; i++) {\n                char path[256] = {0};\n                #pragma warning(suppress: 6011) // dereferencing null\n                r = rt_processes.nameof(pids[i], path, rt_countof(path));\n                if (r != ERROR_NOT_FOUND) {\n                    rt_assert(r == 0 && path[0] != 0);\n                    verbose(\"%6d %s %s\", pids[i], path, rt_strerr(r));\n                }\n            }\n        }\n        rt_heap.deallocate(null, pids);\n    }\n    // test popen()\n    int32_t xc = 0;\n    char data[32 * 1024];\n    rt_stream_memory_if output;\n    rt_streams.write_only(&output, data, rt_countof(data));\n    const char* cmd = \"cmd /c dir 2>nul >nul\";\n    errno_t r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c dir \\\"folder that does not exist\\\\\\\"\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c dir\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c timeout 1\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 1.0E-9);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    #endif\n}\n\n#pragma pop_macro(\"verbose\")\n\n#else\n\nstatic void rt_processes_test(void) { }\n\n#endif\n\nrt_processes_if rt_processes = {\n    .pid                 = rt_processes_pid,\n    .pids                = rt_processes_pids,\n    .nameof              = rt_processes_nameof,\n    .present             = rt_processes_present,\n    .kill                = rt_processes_kill,\n    .kill_all            = rt_processes_kill_all,\n    .is_elevated         = rt_processes_is_elevated,\n    .restart_elevated    = rt_processes_restart_elevated,\n    .run                 = rt_processes_run,\n    .popen               = rt_processes_open,\n    .spawn               = rt_processes_spawn,\n    .name                = rt_processes_name,\n    .test                = rt_processes_test\n};\n\n// _______________________________ rt_static.c ________________________________\n\nstatic void*   rt_static_symbol_reference[1024];\nstatic int32_t rt_static_symbol_reference_count;\n\nvoid* rt_force_symbol_reference(void* symbol) {\n    rt_assert(rt_static_symbol_reference_count <= rt_countof(rt_static_symbol_reference),\n        \"increase size of rt_static_symbol_reference[%d] to at least %d\",\n        rt_countof(rt_static_symbol_reference), rt_static_symbol_reference);\n    if (rt_static_symbol_reference_count < rt_countof(rt_static_symbol_reference)) {\n        rt_static_symbol_reference[rt_static_symbol_reference_count] = symbol;\n//      rt_println(\"rt_static_symbol_reference[%d] = %p\", rt_static_symbol_reference_count,\n//               rt_static_symbol_reference[symbol_reference_count]);\n        rt_static_symbol_reference_count++;\n    }\n    return symbol;\n}\n\n// test rt_static_init() { code } that will be executed in random\n// order but before main()\n\n#ifdef RT_TESTS\n\nstatic int32_t rt_static_init_function_called;\n\nstatic void rt_force_inline rt_static_init_function(void) {\n    rt_static_init_function_called = 1;\n}\n\nrt_static_init(static_init_test) { rt_static_init_function(); }\n\nvoid rt_static_init_test(void) {\n    rt_fatal_if(rt_static_init_function_called != 1,\n        \"static_init_function() expected to be called before main()\");\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nvoid rt_static_init_test(void) {}\n\n#endif\n\n// _________________________________ rt_str.c _________________________________\n\nstatic char* rt_str_drop_const(const char* s) {\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wcast-qual\"\n    #endif\n    return (char*)s;\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic pop\n    #endif\n}\n\nstatic int32_t rt_str_len(const char* s) { return (int32_t)strlen(s); }\n\nstatic int32_t rt_str_utf16len(const uint16_t* utf16) {\n    return (int32_t)wcslen(utf16);\n}\n\nstatic int32_t rt_str_utf8bytes(const char* s, int32_t b) {\n    rt_assert(b >= 1, \"should not be called with bytes < 1\");\n    const uint8_t* const u = (const uint8_t*)s;\n    // based on:\n    // https://stackoverflow.com/questions/66715611/check-for-valid-utf-8-encoding-in-c\n    if (b >= 1 && (u[0] & 0x80u) == 0x00u) {\n        return 1;\n    } else if (b > 1) {\n        uint32_t c = (u[0] << 8) | u[1];\n        // TODO: 0xC080 is a hack - consider removing\n        if (c == 0xC080) { return 2; } // 0xC080 as not zero terminating '\\0'\n        if (0xC280 <= c && c <= 0xDFBF && (c & 0xE0C0) == 0xC080) { return 2; }\n        if (b > 2) {\n            c = (c << 8) | u[2];\n            // reject utf16 surrogates:\n            if (0xEDA080 <= c && c <= 0xEDBFBF) { return 0; }\n            if (0xE0A080 <= c && c <= 0xEFBFBF && (c & 0xF0C0C0) == 0xE08080) {\n                return 3;\n            }\n            if (b > 3) {\n                c = (c << 8) | u[3];\n                if (0xF0908080 <= c && c <= 0xF48FBFBF &&\n                    (c & 0xF8C0C0C0) == 0xF0808080) {\n                    return 4;\n                }\n            }\n        }\n    }\n    return 0; // invalid utf8 sequence\n}\n\nstatic int32_t rt_str_glyphs(const char* utf8, int32_t bytes) {\n    rt_swear(bytes >= 0);\n    bool ok = true;\n    int32_t i = 0;\n    int32_t k = 1;\n    while (i < bytes && ok) {\n        const int32_t b = rt_str.utf8bytes(utf8 + i, bytes - i);\n        ok = 0 < b && i + b <= bytes;\n        if (ok) { i += b; k++; }\n    }\n    return ok ? k - 1 : -1;\n}\n\nstatic void rt_str_lower(char* d, int32_t capacity, const char* s) {\n    int32_t n = rt_str.len(s);\n    rt_swear(capacity > n);\n    for (int32_t i = 0; i < n; i++) { d[i] = (char)tolower(s[i]); }\n    d[n] = 0;\n}\n\nstatic void rt_str_upper(char* d, int32_t capacity, const char* s) {\n    int32_t n = rt_str.len(s);\n    rt_swear(capacity > n);\n    for (int32_t i = 0; i < n; i++) { d[i] = (char)toupper(s[i]); }\n    d[n] = 0;\n}\n\nstatic bool rt_str_starts(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && memcmp(s1, s2, n2) == 0;\n}\n\nstatic bool rt_str_ends(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && memcmp(s1 + n1 - n2, s2, n2) == 0;\n}\n\nstatic bool rt_str_i_starts(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && strnicmp(s1, s2, n2) == 0;\n\n}\n\nstatic bool rt_str_i_ends(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && strnicmp(s1 + n1 - n2, s2, n2) == 0;\n}\n\nstatic int32_t rt_str_utf8_bytes(const uint16_t* utf16, int32_t chars) {\n    // If `chars` argument is -1, the function utf8_bytes includes the zero\n    // terminating character in the conversion and the returned byte count.\n    // Function will fail (return 0) on incomplete surrogate pairs like\n    // 0xD83D without following 0xDC1E https://compart.com/en/unicode/U+1F41E\n    if (chars == 0) { return 0; }\n    if (chars < 0 && utf16[0] == 0x0000) { return 1; }\n    const int32_t required_bytes_count =\n        WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,\n        utf16, chars, null, 0, null, null);\n    if (required_bytes_count == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"WideCharToMultiByte() failed %s\", rt_strerr(r));\n        rt_core.set_err(r);\n    }\n    return required_bytes_count == 0 ? -1 : required_bytes_count;\n}\n\nstatic int32_t rt_str_utf16_chars(const char* utf8, int32_t bytes) {\n    // If `bytes` argument is -1, the function utf16_chars() includes the zero\n    // terminating character in the conversion and the returned character count.\n    if (bytes == 0) { return 0; }\n    if (bytes < 0 && utf8[0] == 0x00) { return 1; }\n    const int32_t required_wide_chars_count =\n        MultiByteToWideChar(CP_UTF8, 0, utf8, bytes, null, 0);\n    if (required_wide_chars_count == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"MultiByteToWideChar() failed %s\", rt_strerr(r));\n        rt_core.set_err(r);\n    }\n    return required_wide_chars_count == 0 ? -1 : required_wide_chars_count;\n}\n\nstatic errno_t rt_str_utf16to8(char* utf8, int32_t capacity,\n        const uint16_t* utf16, int32_t chars) {\n    if (chars == 0) { return 0; }\n    if (chars < 0 && utf16[0] == 0x0000) {\n        rt_swear(capacity >= 1);\n        utf8[0] = 0x00;\n        return 0;\n    }\n    const int32_t required = rt_str.utf8_bytes(utf16, chars);\n    errno_t r = required < 0 ? rt_core.err() : 0;\n    if (r == 0) {\n        rt_swear(required > 0 && capacity >= required);\n        int32_t bytes = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,\n                            utf16, chars, utf8, capacity, null, null);\n        rt_swear(required == bytes);\n    }\n    return r;\n}\n\nstatic errno_t rt_str_utf8to16(uint16_t* utf16, int32_t capacity,\n        const char* utf8, int32_t bytes) {\n    const int32_t required = rt_str.utf16_chars(utf8, bytes);\n    errno_t r = required < 0 ? rt_core.err() : 0;\n    if (r == 0) {\n        rt_swear(required >= 0 && capacity >= required);\n        int32_t count = MultiByteToWideChar(CP_UTF8, 0, utf8, bytes,\n                                            utf16, capacity);\n        rt_swear(required == count);\n#if 0 // TODO: incorrect need output != input\n        if (count > 0 && !IsNormalizedString(NormalizationC, utf16, count)) {\n            rt_core.set_err(0);\n            int32_t n = NormalizeString(NormalizationC, utf16, count, utf16, count);\n            if (n <= 0) {\n                r = rt_core.err();\n                rt_println(\"NormalizeString() failed %s\", rt_strerr(r));\n            }\n        }\n#endif \n    }\n    return r;\n}\n\nstatic bool rt_str_utf16_is_low_surrogate(uint16_t utf16char) {\n    return 0xDC00 <= utf16char && utf16char <= 0xDFFF;\n}\n\nstatic bool rt_str_utf16_is_high_surrogate(uint16_t utf16char) {\n    return 0xD800 <= utf16char && utf16char <= 0xDBFF;\n}\n\nstatic uint32_t rt_str_utf32(const char* utf8, int32_t bytes) {\n    uint32_t utf32 = 0;\n    if ((utf8[0] & 0x80) == 0) {\n        utf32 = utf8[0];\n        rt_swear(bytes == 1);\n    } else if ((utf8[0] & 0xE0) == 0xC0) {\n        utf32  = (utf8[0] & 0x1F) << 6;\n        utf32 |= (utf8[1] & 0x3F);\n        rt_swear(bytes == 2);\n    } else if ((utf8[0] & 0xF0) == 0xE0) {\n        utf32  = (utf8[0] & 0x0F) << 12;\n        utf32 |= (utf8[1] & 0x3F) <<  6;\n        utf32 |= (utf8[2] & 0x3F);\n        rt_swear(bytes == 3);\n    } else if ((utf8[0] & 0xF8) == 0xF0) {\n        utf32  = (utf8[0] & 0x07) << 18;\n        utf32 |= (utf8[1] & 0x3F) << 12;\n        utf32 |= (utf8[2] & 0x3F) <<  6;\n        utf32 |= (utf8[3] & 0x3F);\n        rt_swear(bytes == 4);\n    } else {\n        rt_swear(false);\n    }\n    return utf32;\n}\n\nstatic void rt_str_format_va(char* utf8, int32_t count, const char* format,\n        va_list va) {\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wformat-nonliteral\"\n    #endif\n    vsnprintf(utf8, (size_t)count, format, va);\n    utf8[count - 1] = 0;\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic pop\n    #endif\n}\n\nstatic void rt_str_format(char* utf8, int32_t count, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_str.format_va(utf8, count, format, va);\n    va_end(va);\n}\n\nstatic rt_str1024_t rt_str_error_for_language(int32_t error, LANGID language) {\n    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;\n    HMODULE module = null;\n    HRESULT hr = 0 <= error && error <= 0xFFFF ?\n        HRESULT_FROM_WIN32((uint32_t)error) : (HRESULT)error;\n    if ((error & 0xC0000000U) == 0xC0000000U) {\n        // https://stackoverflow.com/questions/25566234/how-to-convert-specific-ntstatus-value-to-the-hresult\n        static HMODULE ntdll; // RtlNtStatusToDosError implies linking to ntdll\n        if (ntdll == null) { ntdll = GetModuleHandleA(\"ntdll.dll\"); }\n        if (ntdll == null) { ntdll = LoadLibraryA(\"ntdll.dll\"); }\n        module = ntdll;\n        hr = HRESULT_FROM_WIN32(RtlNtStatusToDosError((NTSTATUS)error));\n        flags |= FORMAT_MESSAGE_FROM_HMODULE;\n    }\n    rt_str1024_t text;\n    uint16_t utf16[rt_countof(text.s)];\n    DWORD count = FormatMessageW(flags, module, hr, language,\n            utf16, rt_countof(utf16) - 1, (va_list*)null);\n    utf16[rt_countof(utf16) - 1] = 0; // always\n    // If FormatMessageW() succeeds, the return value is the number of utf16\n    // characters stored in the output buffer, excluding the terminating zero.\n    if (count > 0) {\n        rt_swear(count < rt_countof(utf16));\n        utf16[count] = 0;\n        // remove trailing '\\r\\n'\n        int32_t k = count;\n        if (k > 0 && utf16[k - 1] == '\\n') { utf16[k - 1] = 0; }\n        k = (int32_t)rt_str.len16(utf16);\n        if (k > 0 && utf16[k - 1] == '\\r') { utf16[k - 1] = 0; }\n        char message[rt_countof(text.s)];\n        const int32_t bytes = rt_str.utf8_bytes(utf16, -1);\n        if (bytes >= rt_countof(message)) {\n            rt_str_printf(message, \"error message is too long: %d bytes\", bytes);\n        } else {\n            rt_str.utf16to8(message, rt_countof(message), utf16, -1);\n        }\n        // truncating printf to string:\n        rt_str_printf(text.s, \"0x%08X(%d) \\\"%s\\\"\", error, error, message);\n    } else {\n        rt_str_printf(text.s, \"0x%08X(%d)\", error, error);\n    }\n    return text;\n}\n\nstatic rt_str1024_t rt_str_error(int32_t error) {\n    const LANGID language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);\n    return rt_str_error_for_language(error, language);\n}\n\nstatic rt_str1024_t rt_str_error_nls(int32_t error) {\n    const LANGID language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);\n    return rt_str_error_for_language(error, language);\n}\n\nstatic const char* rt_str_grouping_separator(void) {\n    #ifdef WINDOWS\n        // en-US Windows 10/11:\n        // grouping_separator == \",\"\n        // decimal_separator  == \".\"\n        static char grouping_separator[8];\n        if (grouping_separator[0] == 0x00) {\n            errno_t r = rt_b2e(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,\n                grouping_separator, sizeof(grouping_separator)));\n            rt_swear(r == 0 && grouping_separator[0] != 0);\n        }\n        return grouping_separator;\n    #else\n        // en-US Windows 10/11:\n        // grouping_separator == \"\"\n        // decimal_separator  == \".\"\n        struct lconv *locale_info = localeconv();\n        const char* grouping_separator = null;\n        if (grouping_separator == null) {\n            grouping_separator = locale_info->thousands_sep;\n            swear(grouping_separator != null);\n        }\n        return grouping_separator;\n    #endif\n}\n\n// Posix and Win32 C runtime:\n//   #include <locale.h>\n//   struct lconv *locale_info = localeconv();\n//   const char* grouping_separator = locale_info->thousands_sep;\n//   const char* decimal_separator = locale_info->decimal_point;\n// en-US Windows 1x:\n// grouping_separator == \"\"\n// decimal_separator  == \".\"\n//\n// Win32 API:\n//   rt_b2e(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,\n//       grouping_separator, sizeof(grouping_separator)));\n//   rt_b2e(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,\n//       decimal_separator, sizeof(decimal_separator)));\n// en-US Windows 1x:\n// grouping_separator == \",\"\n// decimal_separator  == \".\"\n\nstatic rt_str64_t rt_str_int64_dg(int64_t v, // digit_grouped\n        bool uint, const char* gs) { // grouping separator: gs\n    // sprintf format %`lld may not be implemented or\n    // does not respect locale or UI separators...\n    // Do it hard way:\n    const int32_t m = (int32_t)strlen(gs);\n    rt_swear(m < 5); // utf-8 4 bytes max\n    // 64 calls per thread 32 or less bytes each because:\n    // \"18446744073709551615\" 21 characters + 6x4 groups:\n    // \"18'446'744'073'709'551'615\" 27 characters\n    rt_str64_t text;\n    enum { max_text_bytes = rt_countof(text.s) };\n    int64_t abs64 = v < 0 ? -v : v; // incorrect for INT64_MIN\n    uint64_t n = uint ? (uint64_t)v :\n        (v != INT64_MIN ? (uint64_t)abs64 : (uint64_t)INT64_MIN);\n    int32_t i = 0;\n    int32_t groups[8]; // 2^63 - 1 ~= 9 x 10^19 upto 7 groups of 3 digits\n    do {\n        groups[i] = n % 1000;\n        n = n / 1000;\n        i++;\n    } while (n > 0);\n    const int32_t gc = i - 1; // group count\n    char* s = text.s;\n    if (v < 0 && !uint) { *s++ = '-'; } // sign\n    int32_t r = max_text_bytes - 1;\n    while (i > 0) {\n        i--;\n        rt_assert(r > 3 + m);\n        if (i == gc) {\n            rt_str.format(s, r, \"%d%s\", groups[i], gc > 0 ? gs : \"\");\n        } else {\n            rt_str.format(s, r, \"%03d%s\", groups[i], i > 0 ? gs : \"\");\n        }\n        int32_t k = (int32_t)strlen(s);\n        r -= k;\n        s += k;\n    }\n    *s = 0;\n    return text;\n}\n\nstatic rt_str64_t rt_str_int64(int64_t v) {\n    return rt_str_int64_dg(v, false, rt_glyph_hair_space);\n}\n\nstatic rt_str64_t rt_str_uint64(uint64_t v) {\n    return rt_str_int64_dg(v, true, rt_glyph_hair_space);\n}\n\nstatic rt_str64_t rt_str_int64_lc(int64_t v) {\n    return rt_str_int64_dg(v, false, rt_str_grouping_separator());\n}\n\nstatic rt_str64_t rt_str_uint64_lc(uint64_t v) {\n    return rt_str_int64_dg(v, true, rt_str_grouping_separator());\n}\n\nstatic rt_str128_t rt_str_fp(const char* format, fp64_t v) {\n    static char decimal_separator[8];\n    if (decimal_separator[0] == 0) {\n        errno_t r = rt_b2e(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,\n            decimal_separator, sizeof(decimal_separator)));\n        rt_swear(r == 0 && decimal_separator[0] != 0);\n    }\n    rt_swear(strlen(decimal_separator) <= 4);\n    rt_str128_t f; // formatted float point\n    // snprintf format does not handle thousands separators on all know runtimes\n    // and respects setlocale() on Un*x systems but in MS runtime only when\n    // _snprintf_l() is used.\n    f.s[0] = 0x00;\n    rt_str.format(f.s, rt_countof(f.s), format, v);\n    f.s[rt_countof(f.s) - 1] = 0x00;\n    rt_str128_t text;\n    char* s = f.s;\n    char* d = text.s;\n    while (*s != 0x00) {\n        if (*s == '.') {\n            const char* sep = decimal_separator;\n            while (*sep != 0x00) { *d++ = *sep++; }\n            s++;\n        } else {\n            *d++ = *s++;\n        }\n    }\n    *d = 0x00;\n    // TODO: It's possible to handle mantissa grouping but...\n    // Not clear if human expects it in 5 digits or 3 digits chunks\n    // and unfortunately locale does not specify how\n    return text;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_str_test(void) {\n    rt_swear(rt_str.len(\"hello\") == 5);\n    rt_swear(rt_str.starts(\"hello world\", \"hello\"));\n    rt_swear(rt_str.ends(\"hello world\", \"world\"));\n    rt_swear(rt_str.istarts(\"hello world\", \"HeLlO\"));\n    rt_swear(rt_str.iends(\"hello world\", \"WoRlD\"));\n    char ls[20] = {0};\n    rt_str.lower(ls, rt_countof(ls), \"HeLlO WoRlD\");\n    rt_swear(strcmp(ls, \"hello world\") == 0);\n    char upper[11] = {0};\n    rt_str.upper(upper, rt_countof(upper), \"hello12345\");\n    rt_swear(strcmp(upper,  \"HELLO12345\") == 0);\n    #pragma push_macro(\"glyph_chinese_one\")\n    #pragma push_macro(\"glyph_chinese_two\")\n    #pragma push_macro(\"glyph_teddy_bear\")\n    #pragma push_macro(\"glyph_ice_cube\")\n    #define glyph_chinese_one \"\\xE5\\xA3\\xB9\"\n    #define glyph_chinese_two \"\\xE8\\xB4\\xB0\"\n    #define glyph_teddy_bear  \"\\xF0\\x9F\\xA7\\xB8\"\n    #define glyph_ice_cube    \"\\xF0\\x9F\\xA7\\x8A\"\n    const char* utf8_str =\n            glyph_teddy_bear\n            \"0\"\n            rt_glyph_chinese_jin4 rt_glyph_chinese_gong\n            \"3456789 \"\n            glyph_ice_cube;\n    rt_swear(rt_str.utf8bytes(\"\\x01\", 1) == 1);\n    rt_swear(rt_str.utf8bytes(\"\\x7F\", 1) == 1);\n    rt_swear(rt_str.utf8bytes(\"\\x80\", 1) == 0);\n//  swear(rt_str.utf8bytes(glyph_chinese_one, 0) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 1) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 2) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 3) == 3);\n    rt_swear(rt_str.utf8bytes(glyph_teddy_bear,  4) == 4);\n    #pragma pop_macro(\"glyph_ice_cube\")\n    #pragma pop_macro(\"glyph_teddy_bear\")\n    #pragma pop_macro(\"glyph_chinese_two\")\n    #pragma pop_macro(\"glyph_chinese_one\")\n    uint16_t wide_str[100] = {0};\n    rt_str.utf8to16(wide_str, rt_countof(wide_str), utf8_str, -1);\n    char utf8[100] = {0};\n    rt_str.utf16to8(utf8, rt_countof(utf8), wide_str, -1);\n    uint16_t utf16[100];\n    rt_str.utf8to16(utf16, rt_countof(utf16), utf8, -1);\n    char narrow_str[100] = {0};\n    rt_str.utf16to8(narrow_str, rt_countof(narrow_str), utf16, -1);\n    rt_swear(strcmp(narrow_str, utf8_str) == 0);\n    char formatted[100];\n    rt_str.format(formatted, rt_countof(formatted), \"n: %d, s: %s\", 42, \"test\");\n    rt_swear(strcmp(formatted, \"n: 42, s: test\") == 0);\n    // numeric values digit grouping format:\n    rt_swear(strcmp(\"0\", rt_str.int64_dg(0, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-1\", rt_str.int64_dg(-1, false, \",\").s) == 0);\n    rt_swear(strcmp(\"999\", rt_str.int64_dg(999, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-999\", rt_str.int64_dg(-999, false, \",\").s) == 0);\n    rt_swear(strcmp(\"1,001\", rt_str.int64_dg(1001, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-1,001\", rt_str.int64_dg(-1001, false, \",\").s) == 0);\n    rt_swear(strcmp(\"18,446,744,073,709,551,615\",\n        rt_str.int64_dg(UINT64_MAX, true, \",\").s) == 0\n    );\n    rt_swear(strcmp(\"9,223,372,036,854,775,807\",\n        rt_str.int64_dg(INT64_MAX, false, \",\").s) == 0\n    );\n    rt_swear(strcmp(\"-9,223,372,036,854,775,808\",\n        rt_str.int64_dg(INT64_MIN, false, \",\").s) == 0\n    );\n    //  see:\n    // https://en.wikipedia.org/wiki/Single-precision_floating-point_format\n    uint32_t pi_fp32 = 0x40490FDBULL; // 3.14159274101257324\n    rt_swear(strcmp(\"3.141592741\",\n                rt_str.fp(\"%.9f\", *(fp32_t*)&pi_fp32).s) == 0,\n          \"%s\", rt_str.fp(\"%.9f\", *(fp32_t*)&pi_fp32).s\n    );\n    //  3.141592741\n    //  ********^ (*** true digits ^ first rounded digit)\n    //    123456 (%.6f)\n    //\n    //  https://en.wikipedia.org/wiki/Double-precision_floating-point_format\n    uint64_t pi_fp64 = 0x400921FB54442D18ULL;\n    rt_swear(strcmp(\"3.141592653589793116\",\n                rt_str.fp(\"%.18f\", *(fp64_t*)&pi_fp64).s) == 0,\n          \"%s\", rt_str.fp(\"%.18f\", *(fp64_t*)&pi_fp64).s\n    );\n    //  3.141592653589793116\n    //  *****************^ (*** true digits ^ first rounded digit)\n    //    123456789012345 (%.15f)\n    //  https://en.wikipedia.org/wiki/Double-precision_floating-point_format\n    //\n    //  actual \"pi\" first 64 digits:\n    //  3.1415926535897932384626433832795028841971693993751058209749445923\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_str_test(void) {}\n\n#endif\n\nrt_str_if rt_str = {\n    .drop_const              = rt_str_drop_const,\n    .len                     = rt_str_len,\n    .len16                   = rt_str_utf16len,\n    .utf8bytes               = rt_str_utf8bytes,\n    .glyphs                  = rt_str_glyphs,\n    .lower                   = rt_str_lower,\n    .upper                   = rt_str_upper,\n    .starts                  = rt_str_starts,\n    .ends                    = rt_str_ends,\n    .istarts                 = rt_str_i_starts,\n    .iends                   = rt_str_i_ends,\n    .utf8_bytes              = rt_str_utf8_bytes,\n    .utf16_chars             = rt_str_utf16_chars,\n    .utf16to8                = rt_str_utf16to8,\n    .utf8to16                = rt_str_utf8to16,\n    .utf16_is_low_surrogate  = rt_str_utf16_is_low_surrogate,\n    .utf16_is_high_surrogate = rt_str_utf16_is_high_surrogate,\n    .utf32                   = rt_str_utf32,\n    .format                  = rt_str_format,\n    .format_va               = rt_str_format_va,\n    .error                   = rt_str_error,\n    .error_nls               = rt_str_error_nls,\n    .grouping_separator      = rt_str_grouping_separator,\n    .int64_dg                = rt_str_int64_dg,\n    .int64                   = rt_str_int64,\n    .uint64                  = rt_str_uint64,\n    .int64_lc                = rt_str_int64,\n    .uint64_lc               = rt_str_uint64,\n    .fp                      = rt_str_fp,\n    .test                    = rt_str_test\n};\n\n// _______________________________ rt_streams.c _______________________________\n\nstatic errno_t rt_streams_memory_read(rt_stream_if* stream, void* data, int64_t bytes,\n        int64_t *transferred) {\n    rt_swear(bytes > 0);\n    rt_stream_memory_if* s = (rt_stream_memory_if*)stream;\n    rt_swear(0 <= s->pos_read && s->pos_read <= s->bytes_read,\n          \"bytes: %lld stream .pos: %lld .bytes: %lld\",\n          bytes, s->pos_read, s->bytes_read);\n    int64_t transfer = rt_min(bytes, s->bytes_read - s->pos_read);\n    memcpy(data, (const uint8_t*)s->data_read + s->pos_read, (size_t)transfer);\n    s->pos_read += transfer;\n    if (transferred != null) { *transferred = transfer; }\n    return 0;\n}\n\nstatic errno_t rt_streams_memory_write(rt_stream_if* stream, const void* data, int64_t bytes,\n        int64_t *transferred) {\n    rt_swear(bytes > 0);\n    rt_stream_memory_if* s = (rt_stream_memory_if*)stream;\n    rt_swear(0 <= s->pos_write && s->pos_write <= s->bytes_write,\n          \"bytes: %lld stream .pos: %lld .bytes: %lld\",\n          bytes, s->pos_write, s->bytes_write);\n    bool overflow = s->bytes_write - s->pos_write <= 0;\n    int64_t transfer = rt_min(bytes, s->bytes_write - s->pos_write);\n    memcpy((uint8_t*)s->data_write + s->pos_write, data, (size_t)transfer);\n    s->pos_write += transfer;\n    if (transferred != null) { *transferred = transfer; }\n    return overflow ? ERROR_INSUFFICIENT_BUFFER : 0;\n}\n\nstatic void rt_streams_read_only(rt_stream_memory_if* s,\n        const void* data, int64_t bytes) {\n    s->stream.read = rt_streams_memory_read;\n    s->stream.write = null;\n    s->data_read = data;\n    s->bytes_read = bytes;\n    s->pos_read = 0;\n    s->data_write = null;\n    s->bytes_write = 0;\n    s->pos_write = 0;\n}\n\nstatic void rt_streams_write_only(rt_stream_memory_if* s,\n        void* data, int64_t bytes) {\n    s->stream.read = null;\n    s->stream.write = rt_streams_memory_write;\n    s->data_read = null;\n    s->bytes_read = 0;\n    s->pos_read = 0;\n    s->data_write = data;\n    s->bytes_write = bytes;\n    s->pos_write = 0;\n}\n\nstatic void rt_streams_read_write(rt_stream_memory_if* s,\n        const void* read, int64_t read_bytes,\n        void* write, int64_t write_bytes) {\n    s->stream.read = rt_streams_memory_read;\n    s->stream.write = rt_streams_memory_write;\n    s->data_read = read;\n    s->bytes_read = read_bytes;\n    s->pos_read = 0;\n    s->pos_read = 0;\n    s->data_write = write;\n    s->bytes_write = write_bytes;\n    s->pos_write = 0;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_streams_test(void) {\n    {   // read test\n        uint8_t memory[256];\n        for (int32_t i = 0; i < rt_countof(memory); i++) { memory[i] = (uint8_t)i; }\n        for (int32_t i = 1; i < rt_countof(memory) - 1; i++) {\n            rt_stream_memory_if ms; // memory stream\n            rt_streams.read_only(&ms, memory, sizeof(memory));\n            uint8_t data[256];\n            for (int32_t j = 0; j < rt_countof(data); j++) { data[j] = 0xFF; }\n            int64_t transferred = 0;\n            errno_t r = ms.stream.read(&ms.stream, data, i, &transferred);\n            rt_swear(r == 0 && transferred == i);\n            for (int32_t j = 0; j < i; j++) { rt_swear(data[j] == memory[j]); }\n            for (int32_t j = i; j < rt_countof(data); j++) { rt_swear(data[j] == 0xFF); }\n        }\n    }\n    {   // write test\n        // TODO: implement\n    }\n    {   // read/write test\n        // TODO: implement\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_streams_test(void) { }\n\n#endif\n\nrt_streams_if rt_streams = {\n    .read_only  = rt_streams_read_only,\n    .write_only = rt_streams_write_only,\n    .read_write = rt_streams_read_write,\n    .test       = rt_streams_test\n};\n\n// _______________________________ rt_threads.c _______________________________\n\n// events:\n\nstatic rt_event_t rt_event_create(void) {\n    HANDLE e = CreateEvent(null, false, false, null);\n    rt_not_null(e);\n    return (rt_event_t)e;\n}\n\nstatic rt_event_t rt_event_create_manual(void) {\n    HANDLE e = CreateEvent(null, true, false, null);\n    rt_not_null(e);\n    return (rt_event_t)e;\n}\n\nstatic void rt_event_set(rt_event_t e) {\n    rt_fatal_win32err(SetEvent((HANDLE)e));\n}\n\nstatic void rt_event_reset(rt_event_t e) {\n    rt_fatal_win32err(ResetEvent((HANDLE)e));\n}\n\nstatic int32_t rt_event_wait_or_timeout(rt_event_t e, fp64_t seconds) {\n    uint32_t ms = seconds < 0 ? INFINITE : (uint32_t)(seconds * 1000.0 + 0.5);\n    DWORD i = WaitForSingleObject(e, ms);\n    rt_swear(i != WAIT_FAILED, \"i: %d\", i);\n    errno_t r = rt_wait_ix2e(i);\n    if (r != 0) { rt_swear(i == WAIT_TIMEOUT || i == WAIT_ABANDONED); }\n    return i == WAIT_TIMEOUT ? -1 : (i == WAIT_ABANDONED ? -2 : i);\n}\n\nstatic void rt_event_wait(rt_event_t e) { rt_event_wait_or_timeout(e, -1); }\n\nstatic int32_t rt_event_wait_any_or_timeout(int32_t n,\n        rt_event_t events[], fp64_t s) {\n    rt_swear(n < 64); // Win32 API limit\n    const uint32_t ms = s < 0 ? INFINITE : (uint32_t)(s * 1000.0 + 0.5);\n    const HANDLE* es = (const HANDLE*)events;\n    DWORD i = WaitForMultipleObjects((DWORD)n, es, false, ms);\n    rt_swear(i != WAIT_FAILED, \"i: %d\", i);\n    errno_t r = rt_wait_ix2e(i);\n    if (r != 0) { rt_swear(i == WAIT_TIMEOUT || i == WAIT_ABANDONED); }\n    return i == WAIT_TIMEOUT ? -1 : (i == WAIT_ABANDONED ? -2 : i);\n}\n\nstatic int32_t rt_event_wait_any(int32_t n, rt_event_t e[]) {\n    return rt_event_wait_any_or_timeout(n, e, -1);\n}\n\nstatic void rt_event_dispose(rt_event_t h) {\n    rt_win32_close_handle(h);\n}\n\n#ifdef RT_TESTS\n\n// test:\n\n// check if the elapsed time is within the expected range\nstatic void rt_event_test_check_time(fp64_t start, fp64_t expected) {\n    fp64_t elapsed = rt_clock.seconds() - start;\n    // Old Windows scheduler is prone to 2x16.6ms ~ 33ms delays (observed)\n    rt_swear(elapsed >= expected - 0.04 && elapsed <= expected + 0.250,\n          \"expected: %f elapsed %f seconds\", expected, elapsed);\n}\n\nstatic void rt_event_test(void) {\n    rt_event_t event = rt_event.create();\n    fp64_t start = rt_clock.seconds();\n    rt_event.set(event);\n    rt_event.wait(event);\n    rt_event_test_check_time(start, 0); // Event should be immediate\n    rt_event.reset(event);\n    start = rt_clock.seconds();\n    const fp64_t timeout_seconds = 1.0 / 8.0;\n    int32_t result = rt_event.wait_or_timeout(event, timeout_seconds);\n    rt_event_test_check_time(start, timeout_seconds);\n    rt_swear(result == -1); // Timeout expected\n    enum { count = 5 };\n    rt_event_t events[count];\n    for (int32_t i = 0; i < rt_countof(events); i++) {\n        events[i] = rt_event.create_manual();\n    }\n    start = rt_clock.seconds();\n    rt_event.set(events[2]); // Set the third event\n    int32_t index = rt_event.wait_any(rt_countof(events), events);\n    rt_swear(index == 2);\n    rt_event_test_check_time(start, 0);\n    rt_swear(index == 2); // Third event should be triggered\n    rt_event.reset(events[2]); // Reset the third event\n    start = rt_clock.seconds();\n    result = rt_event.wait_any_or_timeout(rt_countof(events), events, timeout_seconds);\n    rt_swear(result == -1);\n    rt_event_test_check_time(start, timeout_seconds);\n    rt_swear(result == -1); // Timeout expected\n    // Clean up\n    rt_event.dispose(event);\n    for (int32_t i = 0; i < rt_countof(events); i++) {\n        rt_event.dispose(events[i]);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_event_test(void) { }\n\n#endif\n\nrt_event_if rt_event = {\n    .create              = rt_event_create,\n    .create_manual       = rt_event_create_manual,\n    .set                 = rt_event_set,\n    .reset               = rt_event_reset,\n    .wait                = rt_event_wait,\n    .wait_or_timeout     = rt_event_wait_or_timeout,\n    .wait_any            = rt_event_wait_any,\n    .wait_any_or_timeout = rt_event_wait_any_or_timeout,\n    .dispose             = rt_event_dispose,\n    .test                = rt_event_test\n};\n\n// mutexes:\n\nrt_static_assertion(sizeof(CRITICAL_SECTION) == sizeof(rt_mutex_t));\n\nstatic void rt_mutex_init(rt_mutex_t* m) {\n    CRITICAL_SECTION* cs = (CRITICAL_SECTION*)m;\n    rt_fatal_win32err(InitializeCriticalSectionAndSpinCount(cs, 4096));\n}\n\nstatic void rt_mutex_lock(rt_mutex_t* m) {\n    EnterCriticalSection((CRITICAL_SECTION*)m);\n}\n\nstatic void rt_mutex_unlock(rt_mutex_t* m) {\n    LeaveCriticalSection((CRITICAL_SECTION*)m);\n}\n\nstatic void rt_mutex_dispose(rt_mutex_t* m) {\n    DeleteCriticalSection((CRITICAL_SECTION*)m);\n}\n\n// test:\n\n// check if the elapsed time is within the expected range\nstatic void rt_mutex_test_check_time(fp64_t start, fp64_t expected) {\n    fp64_t elapsed = rt_clock.seconds() - start;\n    // Old Windows scheduler is prone to 2x16.6ms ~ 33ms delays\n    rt_swear(elapsed >= expected - 0.04 && elapsed <= expected + 0.04,\n          \"expected: %f elapsed %f seconds\", expected, elapsed);\n}\n\nstatic void rt_mutex_test_lock_unlock(void* arg) {\n    rt_mutex_t* mutex = (rt_mutex_t*)arg;\n    rt_mutex.lock(mutex);\n    rt_thread.sleep_for(0.01); // Hold the mutex for 10ms\n    rt_mutex.unlock(mutex);\n}\n\nstatic void rt_mutex_test(void) {\n    rt_mutex_t mutex;\n    rt_mutex.init(&mutex);\n    fp64_t start = rt_clock.seconds();\n    rt_mutex.lock(&mutex);\n    rt_mutex.unlock(&mutex);\n    // Lock and unlock should be immediate\n    rt_mutex_test_check_time(start, 0);\n    enum { count = 5 };\n    rt_thread_t ts[count];\n    for (int32_t i = 0; i < rt_countof(ts); i++) {\n        ts[i] = rt_thread.start(rt_mutex_test_lock_unlock, &mutex);\n    }\n    // Wait for all threads to finish\n    for (int32_t i = 0; i < rt_countof(ts); i++) {\n        rt_thread.join(ts[i], -1);\n    }\n    rt_mutex.dispose(&mutex);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\nrt_mutex_if rt_mutex = {\n    .init    = rt_mutex_init,\n    .lock    = rt_mutex_lock,\n    .unlock  = rt_mutex_unlock,\n    .dispose = rt_mutex_dispose,\n    .test    = rt_mutex_test\n};\n\n// threads:\n\nstatic void* rt_thread_ntdll(void) {\n    static HMODULE ntdll;\n    if (ntdll == null) {\n        ntdll = (void*)GetModuleHandleA(\"ntdll.dll\");\n    }\n    if (ntdll == null) {\n        ntdll = rt_loader.open(\"ntdll.dll\", 0);\n    }\n    rt_not_null(ntdll);\n    return ntdll;\n}\n\nstatic fp64_t rt_thread_ns2ms(int64_t ns) {\n    return (fp64_t)ns / (fp64_t)rt_clock.nsec_in_msec;\n}\n\nstatic void rt_thread_set_timer_resolution(uint64_t nanoseconds) {\n    typedef int32_t (*query_timer_resolution_t)(ULONG* minimum_resolution,\n        ULONG* maximum_resolution, ULONG* actual_resolution);\n    typedef int32_t (*set_timer_resolution_t)(ULONG requested_resolution,\n        BOOLEAN set, ULONG* actual_resolution); // ntdll.dll\n    void* nt_dll = rt_thread_ntdll();\n    query_timer_resolution_t query_timer_resolution =  (query_timer_resolution_t)\n        rt_loader.sym(nt_dll, \"NtQueryTimerResolution\");\n    set_timer_resolution_t set_timer_resolution = (set_timer_resolution_t)\n        rt_loader.sym(nt_dll, \"NtSetTimerResolution\");\n    unsigned long min100ns = 16 * 10 * 1000;\n    unsigned long max100ns =  1 * 10 * 1000;\n    unsigned long cur100ns =  0;\n    rt_fatal_if(query_timer_resolution(&min100ns, &max100ns, &cur100ns) != 0);\n    uint64_t max_ns = max100ns * 100uLL;\n//  uint64_t min_ns = min100ns * 100uLL;\n//  uint64_t cur_ns = cur100ns * 100uLL;\n    // max resolution is lowest possible delay between timer events\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f\"\n//          \" ms (milliseconds)\",\n//          rt_thread_ns2ms(min_ns),\n//          rt_thread_ns2ms(max_ns),\n//          rt_thread_ns2ms(cur_ns));\n//  }\n    // note that maximum resolution is actually < minimum\n    nanoseconds = rt_max(max_ns, nanoseconds);\n    unsigned long ns = (unsigned long)((nanoseconds + 99) / 100);\n    rt_fatal_if(set_timer_resolution(ns, true, &cur100ns) != 0);\n    rt_fatal_if(query_timer_resolution(&min100ns, &max100ns, &cur100ns) != 0);\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      min_ns = min100ns * 100uLL;\n//      max_ns = max100ns * 100uLL; // the smallest interval\n//      cur_ns = cur100ns * 100uLL;\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f ms (milliseconds)\",\n//          rt_thread_ns2ms(min_ns),\n//          rt_thread_ns2ms(max_ns),\n//          rt_thread_ns2ms(cur_ns));\n//  }\n}\n\nstatic void rt_thread_power_throttling_disable_for_process(void) {\n    static bool disabled_for_the_process;\n    if (!disabled_for_the_process) {\n        PROCESS_POWER_THROTTLING_STATE pt = { 0 };\n        pt.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;\n        pt.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED;\n        pt.StateMask = 0;\n        rt_fatal_win32err(SetProcessInformation(GetCurrentProcess(),\n            ProcessPowerThrottling, &pt, sizeof(pt)));\n        // PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION\n        // does not work on Win10. There is no easy way to\n        // distinguish Windows 11 from 10 (Microsoft great engineering)\n        pt.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;\n        pt.StateMask = 0;\n        // ignore error on Windows 10:\n        (void)SetProcessInformation(GetCurrentProcess(),\n            ProcessPowerThrottling, &pt, sizeof(pt));\n        disabled_for_the_process = true;\n    }\n}\n\nstatic void rt_thread_power_throttling_disable_for_thread(HANDLE thread) {\n    THREAD_POWER_THROTTLING_STATE pt = { 0 };\n    pt.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;\n    pt.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;\n    pt.StateMask = 0;\n    rt_fatal_win32err(SetThreadInformation(thread,\n        ThreadPowerThrottling, &pt, sizeof(pt)));\n}\n\nstatic void rt_thread_disable_power_throttling(void) {\n    rt_thread_power_throttling_disable_for_process();\n    rt_thread_power_throttling_disable_for_thread(GetCurrentThread());\n}\n\nstatic const char* rt_thread_rel2str(int32_t rel) {\n    switch (rel) {\n        case RelationProcessorCore   : return \"ProcessorCore   \";\n        case RelationNumaNode        : return \"NumaNode        \";\n        case RelationCache           : return \"Cache           \";\n        case RelationProcessorPackage: return \"ProcessorPackage\";\n        case RelationGroup           : return \"Group           \";\n        case RelationProcessorDie    : return \"ProcessorDie    \";\n        case RelationNumaNodeEx      : return \"NumaNodeEx      \";\n        case RelationProcessorModule : return \"ProcessorModule \";\n        default: rt_assert(false, \"fix me\"); return \"???\";\n    }\n}\n\nstatic uint64_t rt_thread_next_physical_processor_affinity_mask(void) {\n    static volatile int32_t initialized;\n    static int32_t init;\n    static int32_t next = 1; // next physical core to use\n    static int32_t cores = 0; // number of physical processors (cores)\n    static uint64_t any;\n    static uint64_t affinity[64]; // mask for each physical processor\n    bool set_to_true = rt_atomics.compare_exchange_int32(&init, false, true);\n    if (set_to_true) {\n        // Concept D: 6 cores, 12 logical processors: 27 lpi entries\n        static SYSTEM_LOGICAL_PROCESSOR_INFORMATION lpi[64];\n        DWORD bytes = 0;\n        GetLogicalProcessorInformation(null, &bytes);\n        rt_assert(bytes % sizeof(lpi[0]) == 0);\n        // number of lpi entries == 27 on 6 core / 12 logical processors system\n        int32_t n = bytes / sizeof(lpi[0]);\n        rt_assert(bytes <= sizeof(lpi), \"increase lpi[%d]\", n);\n        rt_fatal_win32err(GetLogicalProcessorInformation(&lpi[0], &bytes));\n        for (int32_t i = 0; i < n; i++) {\n//          if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//              rt_println(\"[%2d] affinity mask 0x%016llX relationship=%d %s\", i,\n//                  lpi[i].ProcessorMask, lpi[i].Relationship,\n//                  rt_thread_rel2str(lpi[i].Relationship));\n//          }\n            if (lpi[i].Relationship == RelationProcessorCore) {\n                rt_assert(cores < rt_countof(affinity), \"increase affinity[%d]\", cores);\n                if (cores < rt_countof(affinity)) {\n                    any |= lpi[i].ProcessorMask;\n                    affinity[cores] = lpi[i].ProcessorMask;\n                    cores++;\n                }\n            }\n        }\n        initialized = true;\n    } else {\n        while (initialized == 0) { rt_thread.sleep_for(1 / 1024.0); }\n        rt_assert(any != 0); // should not ever happen\n        if (any == 0) { any = (uint64_t)(-1LL); }\n    }\n    uint64_t mask = next < cores ? affinity[next] : any;\n    rt_assert(mask != 0);\n    // assume last physical core is least popular\n    if (next < cores) { next++; } // not circular\n    return mask;\n}\n\nstatic void rt_thread_realtime(void) {\n    rt_fatal_win32err(SetPriorityClass(GetCurrentProcess(),\n        REALTIME_PRIORITY_CLASS));\n    rt_fatal_win32err(SetThreadPriority(GetCurrentThread(),\n        THREAD_PRIORITY_TIME_CRITICAL));\n    rt_fatal_win32err(SetThreadPriorityBoost(GetCurrentThread(),\n        /* bDisablePriorityBoost = */ false));\n    // desired: 0.5ms = 500us (microsecond) = 50,000ns\n    rt_thread_set_timer_resolution((uint64_t)rt_clock.nsec_in_usec * 500ULL);\n    rt_fatal_win32err(SetThreadAffinityMask(GetCurrentThread(),\n        rt_thread_next_physical_processor_affinity_mask()));\n    rt_thread_disable_power_throttling();\n}\n\nstatic void rt_thread_yield(void) { SwitchToThread(); }\n\nstatic rt_thread_t rt_thread_start(void (*func)(void*), void* p) {\n    rt_thread_t t = (rt_thread_t)CreateThread(null, 0,\n        (LPTHREAD_START_ROUTINE)(void*)func, p, 0, null);\n    rt_not_null(t);\n    return t;\n}\n\nstatic bool is_handle_valid(void* h) {\n    DWORD flags = 0;\n    return GetHandleInformation(h, &flags);\n}\n\nstatic errno_t rt_thread_join(rt_thread_t t, fp64_t timeout) {\n    rt_not_null(t);\n    rt_fatal_if(!is_handle_valid(t));\n    const uint32_t ms = timeout < 0 ? INFINITE : (uint32_t)(timeout * 1000.0 + 0.5);\n    DWORD ix = WaitForSingleObject(t, (DWORD)ms);\n    errno_t r = rt_wait_ix2e(ix);\n    rt_assert(r != ERROR_REQUEST_ABORTED, \"AFAIK thread can`t be ABANDONED\");\n    if (r == 0) {\n        rt_win32_close_handle(t);\n    } else {\n        rt_println(\"failed to join thread %p %s\", t, rt_strerr(r));\n    }\n    return r;\n}\n\nstatic void rt_thread_detach(rt_thread_t t) {\n    rt_not_null(t);\n    rt_fatal_if(!is_handle_valid(t));\n    rt_win32_close_handle(t);\n}\n\nstatic void rt_thread_name(const char* name) {\n    uint16_t stack[128];\n    rt_fatal_if(rt_str.len(name) >= rt_countof(stack), \"name too long: %s\", name);\n    rt_str.utf8to16(stack, rt_countof(stack), name, -1);\n    HRESULT r = SetThreadDescription(GetCurrentThread(), stack);\n    // notoriously returns 0x10000000 for no good reason whatsoever\n    rt_fatal_if(!SUCCEEDED(r));\n}\n\nstatic void rt_thread_sleep_for(fp64_t seconds) {\n    rt_assert(seconds >= 0);\n    if (seconds < 0) { seconds = 0; }\n    int64_t ns100 = (int64_t)(seconds * 1.0e+7); // in 0.1 us aka 100ns\n    typedef int32_t (__stdcall *nt_delay_execution_t)(BOOLEAN alertable,\n        PLARGE_INTEGER DelayInterval);\n    static nt_delay_execution_t NtDelayExecution;\n    // delay in 100-ns units. negative value means delay relative to current.\n    LARGE_INTEGER delay = {0}; // delay in 100-ns units.\n    delay.QuadPart = -ns100; // negative value means delay relative to current.\n    if (NtDelayExecution == null) {\n        void* ntdll = rt_thread_ntdll();\n        NtDelayExecution = (nt_delay_execution_t)\n            rt_loader.sym(ntdll, \"NtDelayExecution\");\n        rt_not_null(NtDelayExecution);\n    }\n    // If \"alertable\" is set, sleep_for() can break earlier\n    // as a result of NtAlertThread call.\n    NtDelayExecution(false, &delay);\n}\n\nstatic uint64_t rt_thread_id_of(rt_thread_t t) {\n    return (uint64_t)GetThreadId((HANDLE)t);\n}\n\nstatic uint64_t rt_thread_id(void) {\n    return (uint64_t)GetThreadId(GetCurrentThread());\n}\n\nstatic rt_thread_t rt_thread_self(void) {\n    // GetCurrentThread() returns pseudo-handle, not a real handle\n    // if real handle is ever needed may do\n    // rt_thread_t t = rt_thread.open(rt_thread.id()) and\n    // rt_thread.close(t) instead.\n    return (rt_thread_t)GetCurrentThread();\n}\n\nstatic errno_t rt_thread_open(rt_thread_t *t, uint64_t id) {\n    // GetCurrentThread() returns pseudo-handle, not a real handle.\n    // if real handle is ever needed do rt_thread_id_of() instead\n    // but don't forget to do rt_thread.close() after that.\n    *t = (rt_thread_t)OpenThread(THREAD_ALL_ACCESS, false, (DWORD)id);\n    return *t == null ? rt_core.err() : 0;\n}\n\nstatic void rt_thread_close(rt_thread_t t) {\n    rt_not_null(t);\n    rt_win32_close_handle((HANDLE)t);\n}\n\n#ifdef RT_TESTS\n\n// test: https://en.wikipedia.org/wiki/Dining_philosophers_problem\n\ntypedef struct rt_thread_philosophers_s rt_thread_philosophers_t;\n\ntypedef struct {\n    rt_thread_philosophers_t* ps;\n    rt_mutex_t  fork;\n    rt_mutex_t* left_fork;\n    rt_mutex_t* right_fork;\n    rt_thread_t thread;\n    uint64_t    id;\n} rt_thread_philosopher_t;\n\ntypedef struct rt_thread_philosophers_s {\n    rt_thread_philosopher_t philosopher[3];\n    rt_event_t fed_up[3];\n    uint32_t seed;\n    volatile bool enough;\n} rt_thread_philosophers_t;\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                 \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                             \\\n    }                                                     \\\n} while (0)\n\nstatic void rt_thread_philosopher_think(rt_thread_philosopher_t* p) {\n    verbose(\"philosopher %d is thinking.\", p->id);\n    // Random think time between .1 and .3 seconds\n    fp64_t seconds = (rt_num.random32(&p->ps->seed) % 30 + 1) / 100.0;\n    rt_thread.sleep_for(seconds);\n}\n\nstatic void rt_thread_philosopher_eat(rt_thread_philosopher_t* p) {\n    verbose(\"philosopher %d is eating.\", p->id);\n    // Random eat time between .1 and .2 seconds\n    fp64_t seconds = (rt_num.random32(&p->ps->seed) % 20 + 1) / 100.0;\n    rt_thread.sleep_for(seconds);\n}\n\n// To avoid deadlocks in the Three Philosophers problem, we can implement\n// the Tanenbaum's solution, which ensures that one of the philosophers\n// (e.g., the last one) tries to pick up the right fork first, while the\n// others pick up the left fork first. This breaks the circular wait\n// condition and prevents deadlock.\n\n// If the philosopher is the last one (p->id == n - 1) they will try to pick\n// up the right fork first and then the left fork. All other philosophers will\n// pick up the left fork first and then the right fork, as before. This change\n// ensures that at least one philosopher will be able to eat, breaking the\n// circular wait condition and preventing deadlock.\n\nstatic void rt_thread_philosopher_routine(void* arg) {\n    rt_thread_philosopher_t* p = (rt_thread_philosopher_t*)arg;\n    enum { n = rt_countof(p->ps->philosopher) };\n    rt_thread.name(\"philosopher\");\n    rt_thread.realtime();\n    while (!p->ps->enough) {\n        rt_thread_philosopher_think(p);\n        if (p->id == n - 1) { // Last philosopher picks up the right fork first\n            rt_mutex.lock(p->right_fork);\n            verbose(\"philosopher %d picked up right fork.\", p->id);\n            rt_mutex.lock(p->left_fork);\n            verbose(\"philosopher %d picked up left fork.\", p->id);\n        } else { // Other philosophers pick up the left fork first\n            rt_mutex.lock(p->left_fork);\n            verbose(\"philosopher %d picked up left fork.\", p->id);\n            rt_mutex.lock(p->right_fork);\n            verbose(\"philosopher %d picked up right fork.\", p->id);\n        }\n        rt_thread_philosopher_eat(p);\n        rt_mutex.unlock(p->right_fork);\n        verbose(\"philosopher %d put down right fork.\", p->id);\n        rt_mutex.unlock(p->left_fork);\n        verbose(\"philosopher %d put down left fork.\", p->id);\n        rt_event.set(p->ps->fed_up[p->id]);\n    }\n}\n\nstatic void rt_thread_detached_sleep(void* rt_unused(p)) {\n    rt_thread.sleep_for(1000.0); // seconds\n}\n\nstatic void rt_thread_detached_loop(void* rt_unused(p)) {\n    uint64_t sum = 0;\n    for (uint64_t i = 0; i < UINT64_MAX; i++) { sum += i; }\n    // make sure that compiler won't get rid of the loop:\n    rt_swear(sum == 0x8000000000000001ULL, \"sum: %llu 0x%16llX\", sum, sum);\n}\n\nstatic void rt_thread_test(void) {\n    rt_thread_philosophers_t ps = { .seed = 1 };\n    enum { n = rt_countof(ps.philosopher) };\n    // Initialize mutexes for forks\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        p->id = i;\n        p->ps = &ps;\n        rt_mutex.init(&p->fork);\n        p->left_fork = &p->fork;\n        ps.fed_up[i] = rt_event.create();\n    }\n    // Create and start philosopher threads\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_thread_philosopher_t* r = &ps.philosopher[(i + 1) % n];\n        p->right_fork = r->left_fork;\n        p->thread = rt_thread.start(rt_thread_philosopher_routine, p);\n    }\n    // wait for all philosophers being fed up:\n    for (int32_t i = 0; i < n; i++) { rt_event.wait(ps.fed_up[i]); }\n    ps.enough = true;\n    // join all philosopher threads\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_thread.join(p->thread, -1);\n    }\n    // Dispose of mutexes and events\n    for (int32_t i = 0; i < n; ++i) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_mutex.dispose(&p->fork);\n        rt_event.dispose(ps.fed_up[i]);\n    }\n    // detached threads are hacky and not that swell of an idea\n    // but sometimes can be useful for 1. quick hacks 2. threads\n    // that execute blocking calls that e.g. write logs to the\n    // internet service that hangs.\n    // test detached threads\n    rt_thread_t detached_sleep = rt_thread.start(rt_thread_detached_sleep, null);\n    rt_thread.detach(detached_sleep);\n    rt_thread_t detached_loop = rt_thread.start(rt_thread_detached_loop, null);\n    rt_thread.detach(detached_loop);\n    // leave detached threads sleeping and running till ExitProcess(0)\n    // that should NOT hang.\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"verbose\")\n\n#else\nstatic void rt_thread_test(void) { }\n#endif\n\nrt_thread_if rt_thread = {\n    .start     = rt_thread_start,\n    .join      = rt_thread_join,\n    .detach    = rt_thread_detach,\n    .name      = rt_thread_name,\n    .realtime  = rt_thread_realtime,\n    .yield     = rt_thread_yield,\n    .sleep_for = rt_thread_sleep_for,\n    .id_of     = rt_thread_id_of,\n    .id        = rt_thread_id,\n    .self      = rt_thread_self,\n    .open      = rt_thread_open,\n    .close     = rt_thread_close,\n    .test      = rt_thread_test\n};\n// ________________________________ rt_vigil.c ________________________________\n\n#include <stdio.h>\n#include <string.h>\n\nstatic void rt_vigil_breakpoint_and_abort(void) {\n    rt_debug.breakpoint(); // only if debugger is present\n    rt_debug.raise(rt_debug.exception.noncontinuable);\n    rt_core.abort();\n}\n\nstatic int32_t rt_vigil_failed_assertion(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_debug.println_va(file, line, func, format, va);\n    va_end(va);\n    rt_debug.println(file, line, func, \"assertion failed: %s\\n\", condition);\n    // avoid warnings: conditional expression always true and unreachable code\n    const bool always_true = rt_core.abort != null;\n    if (always_true) { rt_vigil_breakpoint_and_abort(); }\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_termination_va(const char* file, int32_t line,\n        const char* func, const char* condition, errno_t r,\n        const char* format, va_list va) {\n    const int32_t er = rt_core.err();\n    const int32_t en = errno;\n    rt_debug.println_va(file, line, func, format, va);\n    if (r != er && r != 0) {\n        rt_debug.perror(file, line, func, r, \"\");\n    }\n    // report last errors:\n    if (er != 0) { rt_debug.perror(file, line, func, er, \"\"); }\n    if (en != 0) { rt_debug.perrno(file, line, func, en, \"\"); }\n    if (condition != null && condition[0] != 0) {\n        rt_debug.println(file, line, func, \"FATAL: %s\\n\", condition);\n    } else {\n        rt_debug.println(file, line, func, \"FATAL\\n\");\n    }\n    const bool always_true = rt_core.abort != null;\n    if (always_true) { rt_vigil_breakpoint_and_abort(); }\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_termination(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_vigil_fatal_termination_va(file, line, func, condition, 0, format, va);\n    va_end(va);\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_if_error(const char* file, int32_t line,\n    const char* func, const char* condition, errno_t r,\n    const char* format, ...) {\n    if (r != 0) {\n        va_list va;\n        va_start(va, format);\n        rt_vigil_fatal_termination_va(file, line, func, condition, r, format, va);\n        va_end(va);\n    }\n    return 0;\n}\n\n#ifdef RT_TESTS\n\nstatic rt_vigil_if  rt_vigil_test_saved;\nstatic int32_t      rt_vigil_test_failed_assertion_count;\n\n#pragma push_macro(\"rt_vigil\")\n// intimate knowledge of vigil.*() functions used in macro definitions\n#define rt_vigil rt_vigil_test_saved\n\nstatic int32_t rt_vigil_test_failed_assertion(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    rt_fatal_if_not(strcmp(file,  __FILE__) == 0, \"file: %s\", file);\n    rt_fatal_if_not(line > __LINE__, \"line: %s\", line);\n    rt_assert(strcmp(func, \"rt_vigil_test\") == 0, \"func: %s\", func);\n    rt_fatal_if(condition == null || condition[0] == 0);\n    rt_fatal_if(format == null || format[0] == 0);\n    rt_vigil_test_failed_assertion_count++;\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        va_list va;\n        va_start(va, format);\n        rt_debug.println_va(file, line, func, format, va);\n        va_end(va);\n        rt_debug.println(file, line, func, \"assertion failed: %s (expected)\\n\",\n                     condition);\n    }\n    return 0;\n}\n\nstatic int32_t rt_vigil_test_fatal_calls_count;\n\nstatic int32_t rt_vigil_test_fatal_termination(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    const int32_t er = rt_core.err();\n    const int32_t en = errno;\n    rt_assert(er == 2, \"rt_core.err: %d expected 2\", er);\n    rt_assert(en == 2, \"errno: %d expected 2\", en);\n    rt_fatal_if_not(strcmp(file,  __FILE__) == 0, \"file: %s\", file);\n    rt_fatal_if_not(line > __LINE__, \"line: %s\", line);\n    rt_assert(strcmp(func, \"rt_vigil_test\") == 0, \"func: %s\", func);\n    rt_assert(strcmp(condition, \"\") == 0); // not yet used expected to be \"\"\n    rt_assert(format != null && format[0] != 0);\n    rt_vigil_test_fatal_calls_count++;\n    if (rt_debug.verbosity.level > rt_debug.verbosity.trace) {\n        va_list va;\n        va_start(va, format);\n        rt_debug.println_va(file, line, func, format, va);\n        va_end(va);\n        if (er != 0) { rt_debug.perror(file, line, func, er, \"\"); }\n        if (en != 0) { rt_debug.perrno(file, line, func, en, \"\"); }\n        if (condition != null && condition[0] != 0) {\n            rt_debug.println(file, line, func, \"FATAL: %s (testing)\\n\", condition);\n        } else {\n            rt_debug.println(file, line, func, \"FATAL (testing)\\n\");\n        }\n    }\n    return 0;\n}\n\n#pragma pop_macro(\"rt_vigil\")\n\nstatic void rt_vigil_test(void) {\n    rt_vigil_test_saved = rt_vigil;\n    int32_t en = errno;\n    int32_t er = rt_core.err();\n    errno = 2; // ENOENT\n    rt_core.set_err(2); // ERROR_FILE_NOT_FOUND\n    rt_vigil.failed_assertion  = rt_vigil_test_failed_assertion;\n    rt_vigil.fatal_termination = rt_vigil_test_fatal_termination;\n    int32_t count = rt_vigil_test_fatal_calls_count;\n    rt_fatal(\"testing: %s call\", \"fatal()\");\n    rt_assert(rt_vigil_test_fatal_calls_count == count + 1);\n    count = rt_vigil_test_failed_assertion_count;\n    rt_assert(false, \"testing: rt_assert(%s)\", \"false\");\n    #ifdef DEBUG // verify that rt_assert() is only compiled in DEBUG:\n        rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count + 1);\n    #else // not RELEASE buid:\n        rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count);\n    #endif\n    count = rt_vigil_test_failed_assertion_count;\n    rt_swear(false, \"testing: swear(%s)\", \"false\");\n    // swear() is triggered in both debug and release configurations:\n    rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count + 1);\n    errno = en;\n    rt_core.set_err(er);\n    rt_vigil = rt_vigil_test_saved;\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_vigil_test(void) { }\n\n#endif\n\nrt_vigil_if rt_vigil = {\n    .failed_assertion  = rt_vigil_failed_assertion,\n    .fatal_termination = rt_vigil_fatal_termination,\n    .fatal_if_error    = rt_vigil_fatal_if_error,\n    .test              = rt_vigil_test\n};\n\n// ________________________________ rt_win32.c ________________________________\n\nvoid rt_win32_close_handle(void* h) {\n    #pragma warning(suppress: 6001) // shut up overzealous IntelliSense\n    rt_fatal_win32err(CloseHandle((HANDLE)h));\n}\n\n// WAIT_ABANDONED only reported for mutexes not events\n// WAIT_FAILED means event was invalid handle or was disposed\n// by another thread while the calling thread was waiting for it.\n\n/* translate ix to error */\nerrno_t rt_wait_ix2e(uint32_t r) {\n    const int32_t ix = (int32_t)r;\n    return (errno_t)(\n          (int32_t)WAIT_OBJECT_0 <= ix && ix <= WAIT_OBJECT_0 + 63 ? 0 :\n          (ix == WAIT_ABANDONED ? ERROR_REQUEST_ABORTED :\n            (ix == WAIT_TIMEOUT ? ERROR_TIMEOUT :\n              (ix == WAIT_FAILED) ? rt_core.err() : ERROR_INVALID_HANDLE\n            )\n          )\n    );\n}\n\n// ________________________________ rt_work.c _________________________________\n\nstatic void rt_work_queue_no_duplicates(rt_work_t* w) {\n    rt_work_t* e = w->queue->head;\n    bool found = false;\n    while (e != null && !found) {\n        found = e == w;\n        if (!found) { e = e->next; }\n    }\n    rt_swear(!found);\n}\n\nstatic void rt_work_queue_post(rt_work_t* w) {\n    rt_assert(w->queue != null && w != null && w->when >= 0.0);\n    rt_work_queue_t* q = w->queue;\n    rt_atomics.spinlock_acquire(&q->lock);\n    rt_work_queue_no_duplicates(w); // under lock\n    //  Enqueue in time sorted order least ->time first to save\n    //  time searching in fetching from queue which is more frequent.\n    rt_work_t* p = null;\n    rt_work_t* e = q->head;\n    while (e != null && e->when <= w->when) {\n        p = e;\n        e = e->next;\n    }\n    w->next = e;\n    bool head = p == null;\n    if (head) {\n        q->head = w;\n    } else {\n        p->next = w;\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    if (head && q->changed != null) { rt_event.set(q->changed); }\n}\n\nstatic void rt_work_queue_cancel(rt_work_t* w) {\n    rt_swear(!w->canceled && w->queue != null && w->queue->head != null);\n    rt_work_queue_t* q = w->queue;\n    rt_atomics.spinlock_acquire(&q->lock);\n    rt_work_t* p = null;\n    rt_work_t* e = q->head;\n    bool changed = false; // head changed\n    while (e != null && !w->canceled) {\n        if (e == w) {\n            changed = p == null;\n            if (changed) {\n                q->head = e->next;\n        } else {\n                p->next = e->next;\n            }\n            e->next = null;\n            e->canceled = true;\n        } else {\n            p = e;\n            e = e->next;\n        }\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    rt_swear(w->canceled);\n    if (w->done != null) { rt_event.set(w->done); }\n    if (changed && q->changed != null) { rt_event.set(q->changed); }\n}\n\nstatic void rt_work_queue_flush(rt_work_queue_t* q) {\n    while (q->head != null) { rt_work_queue.cancel(q->head); }\n}\n\nstatic bool rt_work_queue_get(rt_work_queue_t* q, rt_work_t* *r) {\n    rt_work_t* w = null;\n    rt_atomics.spinlock_acquire(&q->lock);\n    bool changed = q->head != null && q->head->when <= rt_clock.seconds();\n    if (changed) {\n        w = q->head;\n        q->head = w->next;\n        w->next = null;\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    *r = w;\n    if (changed && q->changed != null) { rt_event.set(q->changed); }\n    return w != null;\n}\n\nstatic void rt_work_queue_call(rt_work_t* w) {\n    if (w->work != null) { w->work(w); }\n    if (w->done != null) { rt_event.set(w->done); }\n}\n\nstatic void rt_work_queue_dispatch(rt_work_queue_t* q) {\n    rt_work_t* w = null;\n    while (rt_work_queue.get(q, &w)) { rt_work_queue.call(w); }\n}\n\nrt_work_queue_if rt_work_queue = {\n    .post     = rt_work_queue_post,\n    .get      = rt_work_queue_get,\n    .call     = rt_work_queue_call,\n    .dispatch = rt_work_queue_dispatch,\n    .cancel   = rt_work_queue_cancel,\n    .flush    = rt_work_queue_flush\n};\n\nstatic void rt_worker_thread(void* p) {\n    rt_thread.name(\"worker\");\n    rt_worker_t* worker = (rt_worker_t*)p;\n    rt_work_queue_t* q = &worker->queue;\n    while (!worker->quit) {\n        rt_work_queue.dispatch(q);\n        fp64_t timeout = -1.0; // forever\n        rt_atomics.spinlock_acquire(&q->lock);\n        if (q->head != null) {\n            timeout = rt_max(0, q->head->when - rt_clock.seconds());\n        }\n        rt_atomics.spinlock_release(&q->lock);\n        // if another item is inserted into head after unlocking\n        // the `wake` event guaranteed to be signalled\n        if (!worker->quit && timeout != 0) {\n            rt_event.wait_or_timeout(worker->wake, timeout);\n        }\n    }\n    rt_work_queue.dispatch(q);\n}\n\nstatic void rt_worker_start(rt_worker_t* worker) {\n    rt_assert(worker->wake == null && !worker->quit);\n    worker->wake  = rt_event.create();\n    worker->queue = (rt_work_queue_t){\n        .head = null, .lock = 0, .changed = worker->wake\n    };\n    worker->thread = rt_thread.start(rt_worker_thread, worker);\n}\n\nstatic errno_t rt_worker_join(rt_worker_t* worker, fp64_t to) {\n    worker->quit = true;\n    rt_event.set(worker->wake);\n    errno_t r = rt_thread.join(worker->thread, to);\n    if (r == 0) {\n        rt_event.dispose(worker->wake);\n        worker->wake = null;\n        worker->thread = null;\n        worker->quit = false;\n        rt_swear(worker->queue.head == null);\n    }\n    return r;\n}\n\nstatic void rt_worker_post(rt_worker_t* worker, rt_work_t* w) {\n    rt_assert(!worker->quit && worker->wake != null && worker->thread != null);\n    w->queue = &worker->queue;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_worker_test(void);\n\nrt_worker_if rt_worker = {\n    .start = rt_worker_start,\n    .post  = rt_worker_post,\n    .join  = rt_worker_join,\n    .test  = rt_worker_test\n};\n\n#ifdef RT_TESTS\n\n// tests:\n\n// keep in mind that rt_println() may be blocking and is a subject\n// of \"astronomical\" wait state times in order of dozens of ms.\n\nstatic int32_t rt_test_called;\n\nstatic void rt_never_called(rt_work_t* rt_unused(w)) {\n    rt_test_called++;\n}\n\nstatic void rt_work_queue_test_1(void) {\n    rt_test_called = 0;\n    // testing insertion time ordering of two events into queue\n    const fp64_t now = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    rt_work_t c1 = {\n        .queue = &q,\n        .work = rt_never_called,\n        .when = now + 1.0\n    };\n    rt_work_t c2 = {\n        .queue = &q,\n        .work = rt_never_called,\n        .when = now + 0.5\n    };\n    rt_work_queue.post(&c1);\n    rt_swear(q.head == &c1 && q.head->next == null);\n    rt_work_queue.post(&c2);\n    rt_swear(q.head == &c2 && q.head->next == &c1);\n    rt_work_queue.flush(&q);\n    // test that canceled events are not dispatched\n    rt_swear(rt_test_called == 0 && c1.canceled && c2.canceled && q.head == null);\n    c1.canceled = false;\n    c2.canceled = false;\n    // test the rt_work_queue.cancel() function\n    rt_work_queue.post(&c1);\n    rt_work_queue.post(&c2);\n    rt_swear(q.head == &c2 && q.head->next == &c1);\n    rt_work_queue.cancel(&c2);\n    rt_swear(c2.canceled && q.head == &c1 && q.head->next == null);\n    c2.canceled = false;\n    rt_work_queue.post(&c2);\n    rt_work_queue.cancel(&c1);\n    rt_swear(c1.canceled && q.head == &c2 && q.head->next == null);\n    rt_work_queue.flush(&q);\n    rt_swear(rt_test_called == 0 && c1.canceled && c2.canceled && q.head == null);\n}\n\n// simple way of passing a single pointer to call_later\n\nstatic fp64_t rt_test_work_start; // makes timing debug traces easier to read\n\nstatic void rt_every_millisecond(rt_work_t* w) {\n    int32_t* i = (int32_t*)w->data;\n    fp64_t now = rt_clock.seconds();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.info) {\n        const fp64_t since_start = now - rt_test_work_start;\n        const fp64_t dt = w->when - rt_test_work_start;\n        rt_println(\"%d now: %.6f time: %.6f\", *i, since_start, dt);\n    }\n    (*i)++;\n    // read rt_clock.seconds() again because rt_println() above could block\n    w->when = rt_clock.seconds() + 0.001;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_work_queue_test_2(void) {\n    rt_thread.realtime();\n    rt_test_work_start = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    // if a single pointer will suffice\n    int32_t i = 0;\n    rt_work_t c = {\n        .queue = &q,\n        .work = rt_every_millisecond,\n        .when = rt_test_work_start + 0.001,\n        .data = &i\n    };\n    rt_work_queue.post(&c);\n    while (q.head != null && i < 8) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(&q);\n    }\n    rt_work_queue.flush(&q);\n    rt_swear(q.head == null);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) {\n        rt_println(\"called: %d times\", i);\n    }\n}\n\n// extending rt_work_t with extra data:\n\ntypedef struct rt_work_ex_s {\n    // nameless union opens up base fields into rt_work_ex_t\n    // it is not necessary at all\n    union {\n        rt_work_t base;\n        struct rt_work_s;\n    };\n    struct { int32_t a; int32_t b; } s;\n    int32_t i;\n} rt_work_ex_t;\n\nstatic void rt_every_other_millisecond(rt_work_t* w) {\n    rt_work_ex_t* ex = (rt_work_ex_t*)w;\n    fp64_t now = rt_clock.seconds();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.info) {\n        const fp64_t since_start = now - rt_test_work_start;\n        const fp64_t dt  = w->when - rt_test_work_start;\n        rt_println(\".i: %d .extra: {.a: %d .b: %d} now: %.6f time: %.6f\",\n                ex->i, ex->s.a, ex->s.b, since_start, dt);\n    }\n    ex->i++;\n    const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n    // read rt_clock.seconds() again because rt_println() above could block\n    w->when = rt_clock.seconds() + 0.002;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_work_queue_test_3(void) {\n    rt_thread.realtime();\n    rt_static_assertion(offsetof(rt_work_ex_t, base) == 0);\n    const fp64_t now = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    rt_work_ex_t ex = {\n        .queue = &q,\n        .work = rt_every_other_millisecond,\n        .when = now + 0.002,\n        .s = { .a = 1, .b = 2 },\n        .i = 0\n    };\n    rt_work_queue.post(&ex.base);\n    while (q.head != null && ex.i < 8) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(&q);\n    }\n    rt_work_queue.flush(&q);\n    rt_swear(q.head == null);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) {\n        rt_println(\"called: %d times\", ex.i);\n    }\n}\n\nstatic void rt_work_queue_test(void) {\n    rt_work_queue_test_1();\n    rt_work_queue_test_2();\n    rt_work_queue_test_3();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\nstatic int32_t rt_test_do_work_called;\n\nstatic void rt_test_do_work(rt_work_t* rt_unused(w)) {\n    rt_test_do_work_called++;\n}\n\nstatic void rt_worker_test(void) {\n//  uncomment one of the following lines to see the output\n//  rt_debug.verbosity.level = rt_debug.verbosity.info;\n//  rt_debug.verbosity.level = rt_debug.verbosity.verbose;\n    rt_work_queue_test(); // first test rt_work_queue\n    rt_worker_t worker = { 0 };\n    rt_worker.start(&worker);\n    rt_work_t asap = {\n        .when = 0, // A.S.A.P.\n        .done = rt_event.create(),\n        .work = rt_test_do_work\n    };\n    rt_work_t later = {\n        .when = rt_clock.seconds() + 0.010, // 10ms\n        .done = rt_event.create(),\n        .work = rt_test_do_work\n    };\n    rt_worker.post(&worker, &asap);\n    rt_worker.post(&worker, &later);\n    // because `asap` and `later` are local variables\n    // code needs to wait for them to be processed inside\n    // this function before they goes out of scope\n    rt_event.wait(asap.done); // await(asap)\n    rt_event.dispose(asap.done); // responsibility of the caller\n    // wait for later:\n    rt_event.wait(later.done); // await(later)\n    rt_event.dispose(later.done); // responsibility of the caller\n    // quit the worker thread:\n    rt_fatal_if_error(rt_worker.join(&worker, -1.0));\n    // does worker respect .when dispatch time?\n    rt_swear(rt_clock.seconds() >= later.when);\n}\n\n#else\n\nstatic void rt_work_queue_test(void) {}\nstatic void rt_worker_test(void) {}\n\n#endif\n\n#endif // rt_implementation\n\n"
  },
  {
    "path": "single_file_lib/ui/ui.h",
    "content": "#ifndef ui_definition\n#define ui_definition\n\n// ___________________________________ ui.h ___________________________________\n\n// alphabetical order is not possible because of headers interdependencies\n// _________________________________ rt_std.h _________________________________\n\n#include <ctype.h>\n#include <errno.h>\n#include <float.h>\n#include <limits.h>\n#include <locale.h>\n#include <malloc.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define rt_stringify(x) #x\n#define rt_tostring(x) rt_stringify(x)\n#define rt_pragma(x) _Pragma(rt_tostring(x))\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#pragma GCC diagnostic ignored \"-Wfour-char-constants\"\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#pragma GCC diagnostic ignored \"-Wmissing-noreturn\"\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"\n#pragma GCC diagnostic ignored \"-Wcast-align\"\n#pragma GCC diagnostic ignored \"-Waddress-of-packed-member\"\n#pragma GCC diagnostic ignored \"-Wused-but-marked-unused\" // because in debug only\n#define rt_msvc_pragma(x)\n#define rt_gcc_pragma(x) rt_pragma(x)\n#else\n#define rt_gcc_pragma(x)\n#define rt_msvc_pragma(x) rt_pragma(x)\n#endif\n\n#ifdef _MSC_VER\n    #define rt_suppress_constant_cond_exp _Pragma(\"warning(suppress: 4127)\")\n#else\n    #define rt_suppress_constant_cond_exp\n#endif\n\n// Type aliases for floating-point types similar to <stdint.h>\ntypedef float  fp32_t;\ntypedef double fp64_t;\n// \"long fp64_t\" is required by C standard but the bitness\n// of it is not specified.\n\n#ifdef __cplusplus\n    #define rt_begin_c extern \"C\" {\n    #define rt_end_c } // extern \"C\"\n#else\n    #define rt_begin_c // C headers compiled as C++\n    #define rt_end_c\n#endif\n\n// rt_countof() and rt_countof() are suitable for\n// small < 2^31 element arrays\n\n#define rt_countof(a) ((int32_t)((int)(sizeof(a) / sizeof((a)[0]))))\n\n#if defined(__GNUC__) || defined(__clang__)\n    #define rt_force_inline __attribute__((always_inline))\n#elif defined(_MSC_VER)\n    #define rt_force_inline __forceinline\n#endif\n\n#ifndef __cplusplus\n    #define null ((void*)0) // better than NULL which is zero\n#else\n    #define null nullptr\n#endif\n\n#if defined(_MSC_VER)\n    #define rt_thread_local __declspec(thread)\n#else\n    #ifndef __cplusplus\n        #define rt_thread_local _Thread_local // C99\n    #else\n        // C++ supports rt_thread_local keyword\n    #endif\n#endif\n\n// rt_begin_packed rt_end_packed\n// usage: typedef rt_begin_packed struct foo_s { ... } rt_end_packed foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_attribute_packed __attribute__((packed))\n#define rt_begin_packed\n#define rt_end_packed rt_attribute_packed\n#else\n#define rt_begin_packed rt_pragma( pack(push, 1) )\n#define rt_end_packed rt_pragma( pack(pop) )\n#define rt_attribute_packed\n#endif\n\n// usage: typedef struct rt_aligned_8 foo_s { ... } foo_t;\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_aligned_8 __attribute__((aligned(8)))\n#elif defined(_MSC_VER)\n#define rt_aligned_8 __declspec(align(8))\n#else\n#define rt_aligned_8\n#endif\n\n\n// In callbacks the formal parameters are\n// frequently unused. Also sometimes parameters\n// are used in debug configuration only (e.g. rt_assert() checks)\n// but not in release.\n// C does not have anonymous parameters like C++\n// Instead of:\n//      void foo(param_type_t param) { (void)param; / *unused */ }\n// use:\n//      vod foo(param_type_t rt_unused(param)) { }\n\n#if defined(__GNUC__) || defined(__clang__)\n#define rt_unused(name) name __attribute__((unused))\n#elif defined(_MSC_VER)\n#define rt_unused(name) _Pragma(\"warning(suppress:  4100)\") name\n#else\n#define rt_unused(name) name\n#endif\n\n// Because MS C compiler is unhappy about alloca() and\n// does not implement (C99 optional) dynamic arrays on the stack:\n\n#define rt_stackalloc(n) (_Pragma(\"warning(suppress: 6255 6263)\") alloca(n))\n\n// alloca() is messy and in general is a not a good idea.\n// try to avoid if possible. Stack sizes vary from 64KB to 8MB in 2024.\n// ________________________________ ui_core.h _________________________________\n\n#include \"rt/rt_std.h\"\n\nrt_begin_c\n\ntypedef struct ui_point_s { int32_t x, y; } ui_point_t;\ntypedef struct ui_rect_s { int32_t x, y, w, h; } ui_rect_t;\ntypedef struct ui_ltbr_s { int32_t left, top, right, bottom; } ui_ltrb_t;\ntypedef struct ui_wh_s   { int32_t w, h; } ui_wh_t;\n\ntypedef struct ui_window_s*  ui_window_t;\ntypedef struct ui_icon_s*    ui_icon_t;\ntypedef struct ui_canvas_s*  ui_canvas_t;\ntypedef struct ui_texture_s* ui_texture_t;\ntypedef struct ui_font_s*    ui_font_t;\ntypedef struct ui_brush_s*   ui_brush_t;\ntypedef struct ui_pen_s*     ui_pen_t;\ntypedef struct ui_cursor_s*  ui_cursor_t;\ntypedef struct ui_region_s*  ui_region_t;\n\ntypedef uintptr_t ui_timer_t; // timer not the same as \"id\" in set_timer()!\n\ntypedef struct ui_bitmap_s { // TODO: ui_ namespace\n    void* pixels;\n    int32_t w; // width\n    int32_t h; // height\n    int32_t bpp;    // \"components\" bytes per pixel\n    int32_t stride; // bytes per scanline rounded up to: (w * bpp + 3) & ~3\n    ui_texture_t texture; // device allocated texture handle\n} ui_bitmap_t;\n\n// ui_margins_t are used for padding and insets and expressed\n// in partial \"em\"s not in pixels, inches or points.\n// Pay attention that \"em\" is not square. \"M\" measurement\n// for most fonts are em.w = 0.5 * em.h\n// .em square pixel size of glyph \"m\"\n// https://en.wikipedia.org/wiki/Em_(typography)\n\ntypedef struct ui_gaps_s { // in partial \"em\"s\n    fp32_t left;\n    fp32_t top;\n    fp32_t right;\n    fp32_t bottom;\n} ui_margins_t;\n\ntypedef struct ui_s {\n    bool (*point_in_rect)(const ui_point_t* p, const ui_rect_t* r);\n    // intersect_rect(null, r0, r1) and intersect_rect(r0, r0, r1) supported.\n    bool (*intersect_rect)(ui_rect_t* destination, const ui_rect_t* r0,\n                                                   const ui_rect_t* r1);\n    ui_rect_t (*combine_rect)(const ui_rect_t* r0, const ui_rect_t* r1);\n    const int32_t infinity; // = INT32_MAX, look better\n    struct { // align bitset\n        int32_t const center; // = 0, default\n        int32_t const left;   // left|top, left|bottom, right|bottom\n        int32_t const top;\n        int32_t const right;  // right|top, right|bottom\n        int32_t const bottom;\n    } const align;\n    struct { // window visibility\n        int32_t const hide;\n        int32_t const normal;   // should be use for first .show()\n        int32_t const minimize; // activate and minimize\n        int32_t const maximize; // activate and maximize\n        int32_t const normal_na;// same as .normal but no activate\n        int32_t const show;     // shows and activates in current size and position\n        int32_t const min_next; // minimize and activate next window in Z order\n        int32_t const min_na;   // minimize but do not activate\n        int32_t const show_na;  // same as .show but no activate\n        int32_t const restore;  // from min/max to normal window size/pos\n        int32_t const defau1t;  // use Windows STARTUPINFO value\n        int32_t const force_min;// minimize even if dispatch thread not responding\n    } const visibility;\n    // TODO: remove or move inside app\n    struct { // message:\n        int32_t const animate;\n        int32_t const opening;\n        int32_t const closing;\n   } const message;\n   // TODO: remove or move inside app\n   struct { // mouse buttons bitset mask\n        struct {\n            int32_t const left;\n            int32_t const right;\n        } button;\n    } const mouse;\n    struct { // window decorations hit test results\n        int32_t const error;            // -2\n        int32_t const transparent;      // -1\n        int32_t const nowhere;          // 0\n        int32_t const client;           // 1\n        int32_t const caption;          // 2\n        int32_t const system_menu;      // 3\n        int32_t const grow_box;         // 4\n        int32_t const menu;             // 5\n        int32_t const horizontal_scroll;// 6\n        int32_t const vertical_scroll;  // 7\n        int32_t const min_button;       // 8\n        int32_t const max_button;       // 9\n        int32_t const left;             // 10\n        int32_t const right;            // 11\n        int32_t const top;              // 12\n        int32_t const top_left;         // 13\n        int32_t const top_right;        // 14\n        int32_t const bottom;           // 15\n        int32_t const bottom_left;      // 16\n        int32_t const bottom_right;     // 17\n        int32_t const border;           // 18\n        int32_t const object;           // 19\n        int32_t const close;            // 20\n        int32_t const help;             // 21\n    } const hit_test;\n    struct { // virtual keyboard keys\n        int32_t const up;\n        int32_t const down;\n        int32_t const left;\n        int32_t const right;\n        int32_t const home;\n        int32_t const end;\n        int32_t const page_up;\n        int32_t const page_down;\n        int32_t const insert;\n        int32_t const del;\n        int32_t const back;\n        int32_t const escape;\n        int32_t const enter;\n        int32_t const plus;\n        int32_t const minus;\n        int32_t const f1;\n        int32_t const f2;\n        int32_t const f3;\n        int32_t const f4;\n        int32_t const f5;\n        int32_t const f6;\n        int32_t const f7;\n        int32_t const f8;\n        int32_t const f9;\n        int32_t const f10;\n        int32_t const f11;\n        int32_t const f12;\n        int32_t const f13;\n        int32_t const f14;\n        int32_t const f15;\n        int32_t const f16;\n        int32_t const f17;\n        int32_t const f18;\n        int32_t const f19;\n        int32_t const f20;\n        int32_t const f21;\n        int32_t const f22;\n        int32_t const f23;\n        int32_t const f24;\n    } const key;\n    struct {\n        int32_t const ok;\n        int32_t const info;\n        int32_t const question;\n        int32_t const warning;\n        int32_t const error;\n    } beep;\n} ui_if;\n\nextern ui_if ui;\n\n// ui_margins_t in \"em\"s:\n//\n// The reason is that UI fonts may become larger smaller\n// for accessibility reasons with the same display\n// density in DPIs. Humanoid would expect the margins around\n// larger font text to grow with font size increase.\n// SwingUI and MacOS is using \"pt\" for padding which does\n// not account to font size changes. MacOS does weird stuff\n// with font increase - it actually decreases GPU resolution.\n// Android uses \"dp\" which is pretty much the same as scaled\n// \"pixels\" on MacOS. Windows used to use \"dialog units\" which\n// is font size based and this is where the idea is inherited from.\n\nrt_end_c\n\n\n// _______________________________ ui_colors.h ________________________________\n\nrt_begin_c\n\ntypedef uint64_t ui_color_t; // top 2 bits determine color format\n\n/* TODO: make ui_color_t uint64_t RGBA or better yet fp32_t RGBA\n         support upto 16-16-16-14(A)bit per pixel color\n         components with 'transparent' aka 'hollow' bit\n*/\n\n#define ui_color_mask        ((ui_color_t)0xC000000000000000ULL)\n#define ui_color_undefined   ((ui_color_t)0x8000000000000000ULL)\n#define ui_color_transparent ((ui_color_t)0x4000000000000000ULL)\n#define ui_color_hdr         ((ui_color_t)0xC000000000000000ULL)\n\n#define ui_color_is_8bit(c)        ( ((c) &  ui_color_mask) == 0)\n#define ui_color_is_hdr(c)         ( ((c) &  ui_color_mask) == ui_color_hdr)\n#define ui_color_is_undefined(c)   ( ((c) &  ui_color_mask) == ui_color_undefined)\n#define ui_color_is_transparent(c) ((((c) &  ui_color_mask) == ui_color_transparent) && \\\n                                   ( ((c) & ~ui_color_mask) == 0))\n// if any other special colors or formats need to be introduced\n// (c) & ~ui_color_mask) has 2^62 possible extensions bits\n\n// ui_color_hdr A - 14 bit, R,G,B - 16 bit, all in range [0..0xFFFF]\n#define ui_color_hdr_a(c)    ((uint16_t)((((c) >> 48) & 0x3FFF) << 2))\n#define ui_color_hdr_r(c)    ((uint16_t)( ((c) >>  0) & 0xFFFF))\n#define ui_color_hdr_g(c)    ((uint16_t)( ((c) >> 16) & 0xFFFF))\n#define ui_color_hdr_b(c)    ((uint16_t)( ((c) >> 32) & 0xFFFF))\n\n#define ui_color_a(c)        ((uint8_t)(((c) >> 24) & 0xFFU))\n#define ui_color_r(c)        ((uint8_t)(((c) >>  0) & 0xFFU))\n#define ui_color_g(c)        ((uint8_t)(((c) >>  8) & 0xFFU))\n#define ui_color_b(c)        ((uint8_t)(((c) >> 16) & 0xFFU))\n\n#define ui_color_is_rgb(c)   ((uint32_t)( (c) & 0x00FFFFFFU))\n#define ui_color_is_rgba(c)  ((uint32_t)( (c) & 0xFFFFFFFFU))\n#define ui_color_is_rgbFF(c) ((uint32_t)(((c) & 0x00FFFFFFU)) | 0xFF000000U)\n\n#define ui_color_rgb(r, g, b) ((ui_color_t)(                     \\\n                              (((uint32_t)(uint8_t)(r))      ) | \\\n                              (((uint32_t)(uint8_t)(g)) <<  8) | \\\n                              (((uint32_t)(uint8_t)(b)) << 16)))\n\n\n#define ui_color_rgba(r, g, b, a)                     \\\n    ( (ui_color_t)(                                   \\\n      (ui_color_rgb(r, g, b)) |                       \\\n      ((ui_color_t)((uint32_t)((uint8_t)(a))) << 24)) \\\n    )\n\nenum {\n    ui_color_id_undefined           =  0,\n    ui_color_id_active_title        =  1,\n    ui_color_id_button_face         =  2,\n    ui_color_id_button_text         =  3,\n    ui_color_id_gray_text           =  4,\n    ui_color_id_highlight           =  5,\n    ui_color_id_highlight_text      =  6,\n    ui_color_id_hot_tracking        =  7,\n    ui_color_id_inactive_title      =  8,\n    ui_color_id_inactive_title_text =  9,\n    ui_color_id_menu_highlight      = 10,\n    ui_color_id_title_text          = 11,\n    ui_color_id_window              = 12,\n    ui_color_id_window_text         = 13,\n    ui_color_id_accent              = 14\n};\n\ntypedef struct ui_control_colors_s {\n    ui_color_t text;\n    ui_color_t background;\n    ui_color_t border;\n    ui_color_t accent; // aka highlight\n    ui_color_t gradient_top;\n    ui_color_t gradient_bottom;\n} control_colors_t;\n\ntypedef struct ui_control_state_colors_s {\n    control_colors_t disabled;\n    control_colors_t enabled;\n    control_colors_t hover;\n    control_colors_t armed;\n    control_colors_t pressed;\n} ui_control_state_colors_t;\n\ntypedef struct ui_colors_s {\n    ui_color_t (*get_color)(int32_t color_id); // ui.colors.*\n    void       (*rgb_to_hsi)(fp64_t r, fp64_t g, fp64_t b, fp64_t *h, fp64_t *s, fp64_t *i);\n    ui_color_t (*hsi_to_rgb)(fp64_t h, fp64_t s, fp64_t i,  uint8_t a);\n    // interpolate():\n    //    0.0 < multiplier < 1.0 excluding boundaries\n    //    alpha is interpolated as well\n    ui_color_t (*interpolate)(ui_color_t c0, ui_color_t c1, fp32_t multiplier);\n    ui_color_t (*gray_with_same_intensity)(ui_color_t c);\n    // multiplier ]0.0..1.0] excluding zero\n    // lighten() and darken() ignore alpha (use interpolate for alpha colors)\n    ui_color_t (*lighten)(ui_color_t rgb, fp32_t multiplier); // interpolate toward white\n    ui_color_t (*darken)(ui_color_t  rgb, fp32_t multiplier); // interpolate toward black\n    ui_color_t (*adjust_saturation)(ui_color_t c,   fp32_t multiplier);\n    ui_color_t (*multiply_brightness)(ui_color_t c, fp32_t multiplier);\n    ui_color_t (*multiply_saturation)(ui_color_t c, fp32_t multiplier);\n    ui_control_state_colors_t* controls; // colors for UI controls\n    ui_color_t const transparent;\n    ui_color_t const none; // aka CLR_INVALID in wingdi.h\n    ui_color_t const text;\n    ui_color_t const white;\n    ui_color_t const black;\n    ui_color_t const red;\n    ui_color_t const green;\n    ui_color_t const blue;\n    ui_color_t const yellow;\n    ui_color_t const cyan;\n    ui_color_t const magenta;\n    ui_color_t const gray;\n    // tone down RGB colors:\n    ui_color_t const tone_white;\n    ui_color_t const tone_red;\n    ui_color_t const tone_green;\n    ui_color_t const tone_blue;\n    ui_color_t const tone_yellow;\n    ui_color_t const tone_cyan;\n    ui_color_t const tone_magenta;\n    // miscellaneous:\n    ui_color_t const orange;\n    ui_color_t const dark_green;\n    ui_color_t const pink;\n    ui_color_t const ochre;\n    ui_color_t const gold;\n    ui_color_t const teal;\n    ui_color_t const wheat;\n    ui_color_t const tan;\n    ui_color_t const brown;\n    ui_color_t const maroon;\n    ui_color_t const barbie_pink;\n    ui_color_t const steel_pink;\n    ui_color_t const salmon_pink;\n    ui_color_t const gainsboro;\n    ui_color_t const light_gray;\n    ui_color_t const silver;\n    ui_color_t const dark_gray;\n    ui_color_t const dim_gray;\n    ui_color_t const light_slate_gray;\n    ui_color_t const slate_gray;\n    /* Named colors */\n    /* Main Panel Backgrounds */\n    ui_color_t const ennui_black; // rgb(18, 18, 18) 0x121212\n    ui_color_t const charcoal;\n    ui_color_t const onyx;\n    ui_color_t const gunmetal;\n    ui_color_t const jet_black;\n    ui_color_t const outer_space;\n    ui_color_t const eerie_black;\n    ui_color_t const oil;\n    ui_color_t const black_coral;\n    ui_color_t const obsidian;\n    /* Secondary Panels or Sidebars */\n    ui_color_t const raisin_black;\n    ui_color_t const dark_charcoal;\n    ui_color_t const dark_jungle_green;\n    ui_color_t const pine_tree;\n    ui_color_t const rich_black;\n    ui_color_t const eclipse;\n    ui_color_t const cafe_noir;\n    /* Flat Buttons */\n    ui_color_t const prussian_blue;\n    ui_color_t const midnight_green;\n    ui_color_t const charleston_green;\n    ui_color_t const rich_black_fogra;\n    ui_color_t const dark_liver;\n    ui_color_t const dark_slate_gray;\n    ui_color_t const black_olive;\n    ui_color_t const cadet;\n    /* Button highlights (hover) */\n    ui_color_t const dark_sienna;\n    ui_color_t const bistre_brown;\n    ui_color_t const dark_puce;\n    ui_color_t const wenge;\n    /* Raised button effects */\n    ui_color_t const dark_scarlet;\n    ui_color_t const burnt_umber;\n    ui_color_t const caput_mortuum;\n    ui_color_t const barn_red;\n    /* Text and Icons */\n    ui_color_t const platinum;\n    ui_color_t const anti_flash_white;\n    ui_color_t const silver_sand;\n    ui_color_t const quick_silver;\n    /* Links and Selections */\n    ui_color_t const dark_powder_blue;\n    ui_color_t const sapphire_blue;\n    ui_color_t const international_klein_blue;\n    ui_color_t const zaffre;\n    /* Additional Colors */\n    ui_color_t const fish_belly;\n    ui_color_t const rusty_red;\n    ui_color_t const falu_red;\n    ui_color_t const cordovan;\n    ui_color_t const dark_raspberry;\n    ui_color_t const deep_magenta;\n    ui_color_t const byzantium;\n    ui_color_t const amethyst;\n    ui_color_t const wisteria;\n    ui_color_t const lavender_purple;\n    ui_color_t const opera_mauve;\n    ui_color_t const mauve_taupe;\n    ui_color_t const rich_lavender;\n    ui_color_t const pansy_purple;\n    ui_color_t const violet_eggplant;\n    ui_color_t const jazzberry_jam;\n    ui_color_t const dark_orchid;\n    ui_color_t const electric_purple;\n    ui_color_t const sky_magenta;\n    ui_color_t const brilliant_rose;\n    ui_color_t const fuchsia_purple;\n    ui_color_t const french_raspberry;\n    ui_color_t const wild_watermelon;\n    ui_color_t const neon_carrot;\n    ui_color_t const burnt_orange;\n    ui_color_t const carrot_orange;\n    ui_color_t const tiger_orange;\n    ui_color_t const giant_onion;\n    ui_color_t const rust;\n    ui_color_t const copper_red;\n    ui_color_t const dark_tangerine;\n    ui_color_t const bright_marigold;\n    ui_color_t const bone;\n    /* Earthy Tones */\n    ui_color_t const sienna;\n    ui_color_t const sandy_brown;\n    ui_color_t const golden_brown;\n    ui_color_t const camel;\n    ui_color_t const burnt_sienna;\n    ui_color_t const khaki;\n    ui_color_t const dark_khaki;\n    /* Greens */\n    ui_color_t const fern_green;\n    ui_color_t const moss_green;\n    ui_color_t const myrtle_green;\n    ui_color_t const pine_green;\n    ui_color_t const jungle_green;\n    ui_color_t const sacramento_green;\n    /* Blues */\n    ui_color_t const yale_blue;\n    ui_color_t const cobalt_blue;\n    ui_color_t const persian_blue;\n    ui_color_t const royal_blue;\n    ui_color_t const iceberg;\n    ui_color_t const blue_yonder;\n    /* Miscellaneous */\n    ui_color_t const cocoa_brown;\n    ui_color_t const cinnamon_satin;\n    ui_color_t const fallow;\n    ui_color_t const cafe_au_lait;\n    ui_color_t const liver;\n    ui_color_t const shadow;\n    ui_color_t const cool_grey;\n    ui_color_t const payne_grey;\n    /* Lighter Tones for Contrast */\n    ui_color_t const timberwolf;\n    ui_color_t const silver_chalice;\n    ui_color_t const roman_silver;\n    /* Dark Mode Specific Highlights */\n    ui_color_t const electric_lavender;\n    ui_color_t const magenta_haze;\n    ui_color_t const cyber_grape;\n    ui_color_t const purple_navy;\n    ui_color_t const liberty;\n    ui_color_t const purple_mountain_majesty;\n    ui_color_t const ceil;\n    ui_color_t const moonstone_blue;\n    ui_color_t const independence;\n} ui_colors_if;\n\nextern ui_colors_if ui_colors;\n\n// TODO:\n// https://ankiewicz.com/colors/\n// https://htmlcolorcodes.com/color-names/\n// it would be super cool to implement a plethora of palettes\n// with named colors and app \"themes\" that can be switched\n\nrt_end_c\n// _______________________________ ui_fuzzing.h _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n// ___________________________________ ui.h ___________________________________\n\n// alphabetical order is not possible because of headers interdependencies\n\n// _________________________________ ui_gdi.h _________________________________\n\nrt_begin_c\n\n// Graphic Device Interface (selected parts of Windows GDI)\n\nenum {  // TODO: into gdi int32_t const\n    ui_gdi_font_quality_default = 0,\n    ui_gdi_font_quality_draft = 1,\n    ui_gdi_font_quality_proof = 2, // anti-aliased w/o ClearType rainbows\n    ui_gdi_font_quality_nonantialiased = 3,\n    ui_gdi_font_quality_antialiased = 4,\n    ui_gdi_font_quality_cleartype = 5,\n    ui_gdi_font_quality_cleartype_natural = 6\n};\n\ntypedef struct ui_fm_s { // font metrics\n    ui_font_t font;\n    ui_wh_t em;        // \"em\" square point size expressed in pixels *)\n    // https://learn.microsoft.com/en-us/windows/win32/gdi/string-widths-and-heights\n    int32_t height;    // font height in pixels\n    int32_t baseline;  // bottom of the glyphs sans descenders (align of multi-font text)\n    int32_t ascent;    // the maximum glyphs extend above the baseline\n    int32_t descent;   // maximum height of descenders\n    int32_t x_height;  // small letters height\n    int32_t cap_em_height;    // Capital letter \"M\" height\n    int32_t internal_leading; // accents and diacritical marks goes there\n    int32_t external_leading;\n    int32_t average_char_width;\n    int32_t max_char_width;\n    int32_t line_gap;  // gap between lines of text\n    ui_wh_t subscript; // height\n    ui_point_t subscript_offset;\n    ui_wh_t superscript;    // height\n    ui_point_t superscript_offset;\n    int32_t underscore;     // height\n    int32_t underscore_position;\n    int32_t strike_through; // height\n    int32_t strike_through_position;\n    int32_t design_units_per_em; // aka EM square ~ 2048\n    ui_rect_t box; // bounding box of the glyphs in design units\n    bool mono;\n} ui_fm_t;\n\n/* see: https://github.com/leok7v/ui/wiki/Typography-Line-Terms\n   https://en.wikipedia.org/wiki/Typeface#Font_metrics\n\n   Example em55x55 H1 font @ 192dpi:\n    _   _                   _              ___    <- y:0\n   (_)_(_)                 | |             ___ /\\    \"diacritics circumflex\"\n     / \\   __ _ _   _ _ __ | |_ ___ _ __       ||\n    / _ \\ / _` | | | | '_ \\| __/ _ \\ '_ \\      ||    .ascend:30\n   / ___ \\ (_| | |_| | |_) | ||  __/ | | |     ||     max extend above baseline\n  /_/   \\_\\__, |\\__, | .__/ \\__\\___|_| |_| ___ || <- .baseline:44\n           __/ | __/ | |                       ||    .descend:11\n          |___/ |___/|_|                   ___ \\/     max height of descenders\n                                                  <- .height:55\n  em: 55x55\n  ascender for \"diacritics circumflex\" is (h:55 - a:30 - d:11) = 14\n*/\n\ntypedef struct ui_gdi_ta_s { // text attributes\n    const ui_fm_t* fm; // font metrics\n    int32_t color_id;  // <= 0 use color\n    ui_color_t color;  // ui_colors.undefined() use color_id\n    bool measure;      // measure only do not draw\n} ui_gdi_ta_t;\n\ntypedef struct {\n    struct {\n        struct {\n            ui_gdi_ta_t const normal;\n            ui_gdi_ta_t const title;\n            ui_gdi_ta_t const rubric;\n            ui_gdi_ta_t const H1;\n            ui_gdi_ta_t const H2;\n            ui_gdi_ta_t const H3;\n        } prop;\n        struct {\n            ui_gdi_ta_t const normal;\n            ui_gdi_ta_t const title;\n            ui_gdi_ta_t const rubric;\n            ui_gdi_ta_t const H1;\n            ui_gdi_ta_t const H2;\n            ui_gdi_ta_t const H3;\n        } mono;\n    } const ta;\n    void (*init)(void);\n    void (*fini)(void);\n    void (*begin)(ui_bitmap_t* bitmap_or_null);\n    // all paint must be done in between\n    void (*end)(void);\n    // TODO: move to ui_colors\n    uint32_t (*color_rgb)(ui_color_t c); // rgb color\n    // bpp bytes (not bits!) per pixel. bpp = -3 or -4 does not swap RGB to BRG:\n    void (*bitmap_init)(ui_bitmap_t* bitmap, int32_t w, int32_t h, int32_t bpp,\n        const uint8_t* pixels);\n    void (*bitmap_init_rgbx)(ui_bitmap_t* bitmap, int32_t w, int32_t h,\n        int32_t bpp, const uint8_t* pixels); // sets all alphas to 0xFF\n    void (*bitmap_dispose)(ui_bitmap_t* bitmap);\n    void (*set_clip)(int32_t x, int32_t y, int32_t w, int32_t h);\n    // use set_clip(0, 0, 0, 0) to clear clip region\n    void (*pixel)(int32_t x, int32_t y, ui_color_t c);\n    void (*line)(int32_t x0, int32_t y1, int32_t x2, int32_t y2,\n                      ui_color_t c);\n    void (*frame)(int32_t x, int32_t y, int32_t w, int32_t h,\n                      ui_color_t c);\n    void (*rect)(int32_t x, int32_t y, int32_t w, int32_t h,\n                      ui_color_t border, ui_color_t fill);\n    void (*fill)(int32_t x, int32_t y, int32_t w, int32_t h, ui_color_t c);\n    void (*poly)(ui_point_t* points, int32_t count, ui_color_t c);\n    void (*circle)(int32_t center_x, int32_t center_y, int32_t odd_radius,\n        ui_color_t border, ui_color_t fill);\n    void (*rounded)(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t odd_radius, ui_color_t border, ui_color_t fill);\n    void (*gradient)(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t rgba_from, ui_color_t rgba_to, bool vertical);\n    // dx, dy, dw, dh destination rectangle\n    // ix, iy, iw, ih rectangle inside pixels[height][width]\n    void (*pixels)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        int32_t bpp, const uint8_t* pixels); // bytes per pixel\n    void (*greyscale)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    void (*bgr)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    void (*bgrx)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels);\n    // alpha() blend only works with device allocated bitmaps\n    void (*alpha)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* bitmap, fp64_t alpha); // alpha blend\n    // bitmap() only works with device allocated bitmaps\n    void (*bitmap)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* bitmap);\n    void (*icon)(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        ui_icon_t icon);\n    // text:\n    void (*cleartype)(bool on); // system wide change: don't use\n    void (*font_smoothing_contrast)(int32_t c); // [1000..2202] or -1 for 1400 default\n    ui_font_t (*create_font)(const char* family, int32_t height, int32_t quality);\n    // custom font, quality: -1 \"as is\"\n    ui_font_t (*font)(ui_font_t f, int32_t height, int32_t quality);\n    void      (*delete_font)(ui_font_t f);\n    void (*dump_fm)(ui_font_t f); // dump font metrics\n    void (*update_fm)(ui_fm_t* fm, ui_font_t f); // fills font metrics\n    ui_wh_t (*text_va)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        const char* format, va_list va);\n    ui_wh_t (*text)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        const char* format, ...);\n    ui_wh_t (*multiline_va)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        int32_t w, const char* format, va_list va); // \"w\" can be zero\n    ui_wh_t (*multiline)(const ui_gdi_ta_t* ta, int32_t x, int32_t y,\n        int32_t w, const char* format, ...);\n    // x[rt_str.glyphs(utf8, bytes)] = {x0, x1, x2, ...}\n    ui_wh_t (*glyphs_placement)(const ui_gdi_ta_t* ta, const char* utf8,\n        int32_t bytes, int32_t x[/*glyphs + 1*/], int32_t glyphs);\n} ui_gdi_if;\n\nextern ui_gdi_if ui_gdi;\n\nrt_end_c\n\n// ________________________________ ui_view.h _________________________________\n\nrt_begin_c\n\nenum ui_view_type_t {\n    ui_view_stack     = 'vwst',\n    ui_view_label     = 'vwlb',\n    ui_view_mbx       = 'vwmb',\n    ui_view_button    = 'vwbt',\n    ui_view_toggle    = 'vwtg',\n    ui_view_slider    = 'vwsl',\n    ui_view_image     = 'vwiv',\n    ui_view_text      = 'vwtx',\n    ui_view_span      = 'vwhs',\n    ui_view_list      = 'vwvs',\n    ui_view_spacer    = 'vwsp',\n    ui_view_scroll    = 'vwsc'\n};\n\ntypedef struct ui_view_s ui_view_t;\n\ntypedef struct ui_view_private_s { // do not access directly\n    char text[1024]; // utf8 zero terminated\n    int32_t strid;    // 0 for not yet localized, -1 no localization\n    fp64_t armed_until; // rt_clock.seconds() - when to release\n    fp64_t hover_when;  // time in seconds when to call hovered()\n    // use: ui_view.string(v) and ui_view.set_string()\n} ui_view_private_t;\n\ntypedef struct ui_view_text_metrics_s { // ui_view.measure_text() fills these attributes:\n    ui_wh_t    wh; // text width and height\n    ui_point_t xy; // text offset inside view\n    bool multiline; // text contains \"\\n\"\n} ui_view_text_metrics_t;\n\ntypedef struct ui_view_s {\n    enum ui_view_type_t type;\n    ui_view_private_t p; // private\n    void (*init)(ui_view_t* v); // called once before first layout\n    ui_view_t* parent;\n    ui_view_t* child; // first child, circular doubly linked list\n    ui_view_t* prev;  // left or top sibling\n    ui_view_t* next;  // right or top sibling\n    int32_t x;\n    int32_t y;\n    int32_t w;\n    int32_t h;\n    ui_margins_t insets;\n    ui_margins_t padding;\n    ui_view_text_metrics_t text;\n    // see ui.alignment values\n    int32_t align; // align inside parent\n    int32_t text_align; // align of the text inside control\n    int32_t max_w; // > 0 maximum width in pixels the view agrees to\n    int32_t max_h; // > 0 maximum height in pixels\n    fp32_t  min_w_em; // > 0 minimum width  of a view in \"em\"s\n    fp32_t  min_h_em; // > 0 minimum height of a view in \"em\"s\n    ui_icon_t icon; // used instead of text if != null\n    // updated on layout() call\n    const ui_fm_t* fm; // font metrics\n    int32_t  shortcut; // keyboard shortcut\n    void* that;  // for the application use\n    void (*notify)(ui_view_t* v, void* p); // for the application use\n    // two pass layout: measure() .w, .h layout() .x .y\n    // first  measure() bottom up - children.layout before parent.layout\n    // second layout() top down - parent.layout before children.layout\n    // before methods: called before measure()/layout()/paint()\n    void (*prepare)(ui_view_t* v);    // called before measure()\n    void (*measure)(ui_view_t* v);    // determine w, h (bottom up)\n    void (*measured)(ui_view_t* v);   // called after measure()\n    void (*layout)(ui_view_t* v);     // set x, y possibly adjust w, h (top down)\n    void (*composed)(ui_view_t* v);   // after layout() is done (laid out)\n    void (*erase)(ui_view_t* v);      // called before paint()\n    void (*paint)(ui_view_t* v);\n    void (*painted)(ui_view_t* v);  // called after paint()\n    // composed() is effectively called right before paint() and\n    // can be used to prepare for painting w/o need to override paint()\n    void (*debug_paint)(ui_view_t* v); // called if .debug is set to true\n    // any message:\n    bool (*message)(ui_view_t* v, int32_t message, int64_t wp, int64_t lp,\n        int64_t* rt); // return true and value in rt to stop processing\n    void (*click)(ui_view_t* v);    // ui click callback - view action\n    void (*format)(ui_view_t* v);   // format a value to text (e.g. slider)\n    void (*callback)(ui_view_t* v); // state change callback\n    void (*mouse_scroll)(ui_view_t* v, ui_point_t dx_dy); // touchpad scroll\n    void (*mouse_hover)(ui_view_t* v); // hover over\n    void (*mouse_move)(ui_view_t* v);\n    void (*double_click)(ui_view_t* v, int32_t ix);\n    // tap(ui, button_index) press(ui, button_index) see note below\n    // button index 0: left, 1: middle, 2: right\n    // bottom up (leaves to root or children to parent)\n    // return true if consumed (halts further calls up the tree)\n    bool (*tap)(ui_view_t* v, int32_t ix, bool pressed); // single click/tap inside ui\n    bool (*long_press)(ui_view_t* v, int32_t ix); // two finger click/tap or long press\n    bool (*double_tap)(ui_view_t* v, int32_t ix); // legacy double click\n    bool (*context_menu)(ui_view_t* v); // right mouse click or long press\n    void (*focus_gained)(ui_view_t* v);\n    void (*focus_lost)(ui_view_t* v);\n    // translated from key pressed/released to utf8:\n    void (*character)(ui_view_t* v, const char* utf8);\n    bool (*key_pressed)(ui_view_t* v, int64_t key);  // return true to stop\n    bool (*key_released)(ui_view_t* v, int64_t key); // processing\n    // timer() every_100ms() and every_sec() called\n    // even for hidden and disabled views\n    void (*timer)(ui_view_t* v, ui_timer_t id);\n    void (*every_100ms)(ui_view_t* v); // ~10 x times per second\n    void (*every_sec)(ui_view_t* v); // ~once a second\n    int64_t (*hit_test)(const ui_view_t* v, ui_point_t pt);\n    struct {\n        bool hidden;    // measure()/ layout() paint() is not called on\n        bool disabled;  // mouse, keyboard, key_up/down not called on\n        bool armed;     // button is pressed but not yet released\n        bool hover;     // cursor is hovering over the control\n        bool pressed;   // for ui_button_t and ui_toggle_t\n    } state;\n    // TODO: instead of flat color scheme: undefined colors for\n    // border rounded gradient etc.\n    bool flat;                // no-border appearance of controls\n    bool flip;                // flip button pressed / released\n    bool focusable;           // can be target for keyboard focus\n    bool highlightable;       // paint highlight rectangle when hover over label\n    ui_color_t color;         // interpretation depends on view type\n    int32_t    color_id;      // 0 is default meaning use color\n    ui_color_t background;    // interpretation depends on view type\n    int32_t    background_id; // 0 is default meaning use background\n    char hint[256]; // tooltip hint text (to be shown while hovering over view)\n    struct {\n        struct {\n            bool prc; // paint rect\n            bool mt;  // measure text\n        } trace;\n        struct { // after painted():\n            bool call;    // v->debug_paint()\n            bool margins; // call debug_paint_margins()\n            bool fm;      // paint font metrics\n        } paint;\n        const char* id; // for debugging purposes\n    } debug; // debug flags\n} ui_view_t;\n\n// tap() / press() APIs guarantee that single tap() is not coming\n// before fp64_t tap/click in expense of fp64_t click delay (0.5 seconds)\n// which is OK for buttons and many other UI controls but absolutely not\n// OK for text editing. Thus edit uses raw mouse events to react\n// on clicks and fp64_t clicks.\n\ntypedef struct ui_view_if {\n    // children va_args must be null terminated\n    ui_view_t* (*add)(ui_view_t* parent, ...);\n    void (*add_first)(ui_view_t* parent, ui_view_t* child);\n    void (*add_last)(ui_view_t*  parent, ui_view_t* child);\n    void (*add_after)(ui_view_t* child,  ui_view_t* after);\n    void (*add_before)(ui_view_t* child, ui_view_t* before);\n    void (*remove)(ui_view_t* v); // removes view from it`s parent\n    void (*remove_all)(ui_view_t* parent); // removes all children\n    void (*disband)(ui_view_t* parent); // removes all children recursively\n    bool (*is_parent_of)(const ui_view_t* p, const ui_view_t* c);\n    bool (*inside)(const ui_view_t* v, const ui_point_t* pt);\n    ui_ltrb_t (*margins)(const ui_view_t* v, const ui_margins_t* g); // to pixels\n    void (*inbox)(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* insets);\n    void (*outbox)(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* padding);\n    void (*set_text)(ui_view_t* v, const char* format, ...);\n    void (*set_text_va)(ui_view_t* v, const char* format, va_list va);\n    // ui_view.invalidate() prone to 30ms delays don't use in r/t video code\n    // ui_view.invalidate(v, ui_app.crc) invalidates whole client rect but\n    // ui_view.redraw() (fast non blocking) is much better instead\n    void (*invalidate)(const ui_view_t* v, const ui_rect_t* rect_or_null);\n    bool (*is_orphan)(const ui_view_t* v);   // view parent chain has null\n    bool (*is_hidden)(const ui_view_t* v);   // view or any parent is hidden\n    bool (*is_disabled)(const ui_view_t* v); // view or any parent is disabled\n    bool (*is_control)(const ui_view_t* v);\n    bool (*is_container)(const ui_view_t* v);\n    bool (*is_spacer)(const ui_view_t* v);\n    const char* (*string)(ui_view_t* v);  // returns localized text\n    void (*timer)(ui_view_t* v, ui_timer_t id);\n    void (*every_sec)(ui_view_t* v);\n    void (*every_100ms)(ui_view_t* v);\n    int64_t (*hit_test)(const ui_view_t* v, ui_point_t pt);\n    // key_pressed() key_released() return true to stop further processing\n    bool (*key_pressed)(ui_view_t* v, int64_t v_key);\n    bool (*key_released)(ui_view_t* v, int64_t v_key);\n    void (*character)(ui_view_t* v, const char* utf8);\n    void (*paint)(ui_view_t* v);\n    bool (*has_focus)(const ui_view_t* v); // ui_app.focused() && ui_app.focus == v\n    void (*set_focus)(ui_view_t* view_or_null);\n    void (*lose_hidden_focus)(ui_view_t* v);\n    void (*hovering)(ui_view_t* v, bool start);\n    void (*mouse_hover)(ui_view_t* v); // hover over\n    void (*mouse_move)(ui_view_t* v);\n    void (*mouse_scroll)(ui_view_t* v, ui_point_t dx_dy); // touchpad scroll\n    ui_wh_t (*text_metrics_va)(int32_t x, int32_t y, bool multiline, int32_t w,\n        const ui_fm_t* fm, const char* format, va_list va);\n    ui_wh_t (*text_metrics)(int32_t x, int32_t y, bool multiline, int32_t w,\n        const ui_fm_t* fm, const char* format, ...);\n    void (*text_measure)(ui_view_t* v, const char* s,\n        ui_view_text_metrics_t* tm);\n    void (*text_align)(ui_view_t* v, ui_view_text_metrics_t* tm);\n    void (*measure_text)(ui_view_t* v); // fills v->text.mt and .xy\n    // measure_control(): control is special case with v->text.mt and .xy\n    void (*measure_control)(ui_view_t* v);\n    void (*measure_children)(ui_view_t* v);\n    void (*layout_children)(ui_view_t* v);\n    void (*measure)(ui_view_t* v);\n    void (*layout)(ui_view_t* v);\n    void (*hover_changed)(ui_view_t* v);\n    bool (*is_shortcut_key)(ui_view_t* v, int64_t key);\n    bool (*context_menu)(ui_view_t* v);\n    // `ix` 0: left 1: middle 2: right\n    bool (*tap)(ui_view_t* v, int32_t ix, bool pressed);\n    bool (*long_press)(ui_view_t* v, int32_t ix);\n    bool (*double_tap)(ui_view_t* v, int32_t ix);\n    bool (*message)(ui_view_t* v, int32_t m, int64_t wp, int64_t lp, int64_t* ret);\n    void (*debug_paint_margins)(ui_view_t* v); // insets padding\n    void (*debug_paint_fm)(ui_view_t* v);   // text font metrics\n    void (*test)(void);\n} ui_view_if;\n\nextern ui_view_if ui_view;\n\n// view children iterator:\n\n#define ui_view_for_each_begin(v, it) do {       \\\n    ui_view_t* it = (v)->child;                  \\\n    if (it != null) {                            \\\n        do {                                     \\\n\n\n#define ui_view_for_each_end(v, it)              \\\n            it = it->next;                       \\\n        } while (it != (v)->child);              \\\n    }                                            \\\n} while (0)\n\n#define ui_view_for_each(v, it, ...) \\\n    ui_view_for_each_begin(v, it)    \\\n    { __VA_ARGS__ }                  \\\n    ui_view_for_each_end(v, it)\n\n#define ui_view_debug_id(v) \\\n    ((v)->debug.id != null ? (v)->debug.id : (v)->p.text)\n\n// #define code(statements) statements\n//\n// used as:\n// {\n//     macro({\n//        foo();\n//        bar();\n//     })\n// }\n//\n// except in m4 preprocessor loses new line\n// between foo() and bar() and makes debugging and\n// using __LINE__ difficult to impossible.\n//\n// Also\n// #define code(...) { __VA_ARGS__ }\n// is way easier on preprocessor\n\n// ui_view_insets (fractions of 1/2 to keep float calculations precise):\n#define ui_view_i_lr (0.750f) // 3/4 of \"em.w\" on left and right\n#define ui_view_i_tb (0.125f) // 1/8 em\n\n// ui_view_padding\n#define ui_view_p_lr (0.375f)\n#define ui_view_p_tb (0.250f)\n\n#define ui_view_call_init(v) do {                   \\\n    if ((v)->init != null) {                        \\\n        void (*_init_)(ui_view_t* _v_) = (v)->init; \\\n        (v)->init = null; /* before! call */        \\\n        _init_((v));                                \\\n    }                                               \\\n} while (0)\n\n\nrt_end_c\n// _____________________________ ui_containers.h ______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n\nrt_begin_c\n\ntypedef struct ui_view_s ui_view_t;\n\n// Usage:\n//\n// ui_view_t* stack  = ui_view(stack);\n// ui_view_t* horizontal = ui_view(ui_view_span);\n// ui_view_t* vertical   = ui_view(ui_view_list);\n//\n// containers automatically layout child views\n// similar to SwiftUI HStack and VStack taking .align\n// .insets and .padding into account.\n//\n// Container positions every child views in the center,\n// top bottom left right edge or any of 4 corners\n// depending on .align values.\n// if child view has .max_w or .max_h set to ui.infinity == INT32_MAX\n// the views are expanded to fill the container in specified\n// direction. If child .max_w or .max_h is set to > .w or .h\n// the child view .w .h measurement are expanded accordingly.\n//\n// All containers are transparent and inset by 1/4 of an \"em\"\n// Except ui_app.root,caption,content which are also containers\n// but are not inset or padded and have default background color.\n//\n// Application implementer can override this after\n//\n// void opened(void) {\n//     ui_view.add(ui_app.view, ..., null);\n//     ui_app.view->insets = (ui_margins_t) {\n//         .left  = 0.25, .top    = 0.25,\n//         .right = 0.25, .bottom = 0.25 };\n//     ui_app.view->color = ui_colors.dark_scarlet;\n// }\n\ntypedef struct ui_view_s ui_view_t;\n\n#define ui_view(view_type) {            \\\n    .type = (ui_view_ ## view_type),    \\\n    .init = ui_view_init_ ## view_type, \\\n    .fm   = &ui_app.fm.prop.normal,     \\\n    .color = ui_color_transparent,      \\\n    .color_id = 0                       \\\n}\n\nvoid ui_view_init_stack(ui_view_t* v);\nvoid ui_view_init_span(ui_view_t* v);\nvoid ui_view_init_list(ui_view_t* v);\nvoid ui_view_init_spacer(ui_view_t* v);\n\nrt_end_c\n// ______________________________ ui_edit_doc.h _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n\nrt_begin_c\n\ntypedef struct ui_edit_str_s ui_edit_str_t;\n\ntypedef struct ui_edit_doc_s ui_edit_doc_t;\n\ntypedef struct ui_edit_notify_s ui_edit_notify_t;\n\ntypedef struct ui_edit_to_do_s ui_edit_to_do_t;\n\ntypedef struct ui_edit_pg_s { // page/glyph coordinates\n    // humans used to line:column coordinates in text\n    int32_t pn; // zero based paragraph number (\"line number\")\n    int32_t gp; // zero based glyph position (\"column\")\n} ui_edit_pg_t;\n\ntypedef union rt_begin_packed ui_edit_range_s {\n    struct { ui_edit_pg_t from; ui_edit_pg_t to; };\n    ui_edit_pg_t a[2];\n} rt_end_packed ui_edit_range_t; // \"from\"[0] \"to\"[1]\n\ntypedef struct ui_edit_text_s {\n    int32_t np;   // number of paragraphs\n    ui_edit_str_t* ps; // ps[np] paragraphs\n} ui_edit_text_t;\n\ntypedef struct ui_edit_notify_info_s {\n    bool ok; // false if ui_edit_view.replace() failed (bad utf8 or no memory)\n    const ui_edit_doc_t*   const d;\n    const ui_edit_range_t* const r; // range to be replaced\n    const ui_edit_range_t* const x; // extended range (replacement)\n    const ui_edit_text_t*  const t; // replacement text\n    // d->text.np number of paragraphs may change after replace\n    // before/after: [pnf..pnt] is inside [0..d->text.np-1]\n    int32_t const pnf; // paragraph number from\n    int32_t const pnt; // paragraph number to. (inclusive)\n    // one can safely assume that ps[pnf] was modified\n    // except empty range replace with empty text (which shouldn't be)\n    // d->text.ps[pnf..pnf + deleted] were deleted\n    // d->text.ps[pnf..pnf + inserted] were inserted\n    int32_t const deleted;  // number of deleted  paragraphs (before: 0)\n    int32_t const inserted; // paragraph inserted paragraphs (before: 0)\n} ui_edit_notify_info_t;\n\ntypedef struct ui_edit_notify_s { // called before and after replace()\n    void (*before)(ui_edit_notify_t* notify, const ui_edit_notify_info_t* ni);\n    // after() is called even if replace() failed with ok: false\n    void (*after)(ui_edit_notify_t* notify, const ui_edit_notify_info_t* ni);\n} ui_edit_notify_t;\n\ntypedef struct ui_edit_listener_s ui_edit_listener_t;\n\ntypedef struct ui_edit_listener_s {\n    ui_edit_notify_t* notify;\n    ui_edit_listener_t* prev;\n    ui_edit_listener_t* next;\n} ui_edit_listener_t;\n\ntypedef struct ui_edit_to_do_s { // undo/redo action\n    ui_edit_range_t  range;\n    ui_edit_text_t   text;\n    ui_edit_to_do_t* next; // inside undo or redo list\n} ui_edit_to_do_t;\n\ntypedef struct ui_edit_doc_s {\n    ui_edit_text_t   text;\n    ui_edit_to_do_t* undo; // undo stack\n    ui_edit_to_do_t* redo; // redo stack\n    ui_edit_listener_t* listeners;\n} ui_edit_doc_t;\n\ntypedef struct ui_edit_doc_if {\n    // init(utf8, bytes, heap:false) must have longer lifetime\n    // than document, otherwise use heap: true to copy\n    bool    (*init)(ui_edit_doc_t* d, const char* utf8_or_null,\n                    int32_t bytes, bool heap);\n    bool    (*replace)(ui_edit_doc_t* d, const ui_edit_range_t* r,\n                const char* utf8, int32_t bytes);\n    int32_t (*bytes)(const ui_edit_doc_t* d, const ui_edit_range_t* range);\n    bool    (*copy_text)(ui_edit_doc_t* d, const ui_edit_range_t* range,\n                ui_edit_text_t* text); // retrieves range into string\n    int32_t (*utf8bytes)(const ui_edit_doc_t* d, const ui_edit_range_t* range);\n    // utf8 must be at least ui_edit_doc.utf8bytes()\n    void    (*copy)(ui_edit_doc_t* d, const ui_edit_range_t* range,\n                char* utf8, int32_t bytes);\n    // undo() and push reverse into redo stack\n    bool (*undo)(ui_edit_doc_t* d); // false if there is nothing to redo\n    // redo() and push reverse into undo stack\n    bool (*redo)(ui_edit_doc_t* d); // false if there is nothing to undo\n    bool (*subscribe)(ui_edit_doc_t* d, ui_edit_notify_t* notify);\n    void (*unsubscribe)(ui_edit_doc_t* d, ui_edit_notify_t* notify);\n    void (*dispose_to_do)(ui_edit_to_do_t* to_do);\n    void (*dispose)(ui_edit_doc_t* d);\n    void (*test)(void);\n} ui_edit_doc_if;\n\nextern ui_edit_doc_if ui_edit_doc;\n\ntypedef struct ui_edit_range_if {\n    int (*compare)(const ui_edit_pg_t pg1, const ui_edit_pg_t pg2);\n    ui_edit_range_t (*order)(const ui_edit_range_t r);\n    bool            (*is_valid)(const ui_edit_range_t r);\n    bool            (*is_empty)(const ui_edit_range_t r);\n    uint64_t        (*uint64)(const ui_edit_pg_t pg); // (p << 32 | g)\n    ui_edit_pg_t    (*pg)(uint64_t ui64); // p: (ui64 >> 32) g: (int32_t)ui64\n    bool            (*inside)(const ui_edit_text_t* t,\n                              const ui_edit_range_t r);\n    ui_edit_range_t (*intersect)(const ui_edit_range_t r1,\n                                 const ui_edit_range_t r2);\n    const ui_edit_range_t* const invalid_range; // {{-1,-1},{-1,-1}}\n} ui_edit_range_if;\n\nextern ui_edit_range_if ui_edit_range;\n\ntypedef struct ui_edit_text_if {\n    bool    (*init)(ui_edit_text_t* t, const char* utf, int32_t b, bool heap);\n\n    int32_t (*bytes)(const ui_edit_text_t* t, const ui_edit_range_t* r);\n    // end() last paragraph, last glyph in text\n    ui_edit_pg_t    (*end)(const ui_edit_text_t* t);\n    ui_edit_range_t (*end_range)(const ui_edit_text_t* t);\n    ui_edit_range_t (*all_on_null)(const ui_edit_text_t* t,\n                                   const ui_edit_range_t* r);\n    ui_edit_range_t (*ordered)(const ui_edit_text_t* t,\n                               const ui_edit_range_t* r);\n    bool    (*dup)(ui_edit_text_t* t, const ui_edit_text_t* s);\n    bool    (*equal)(const ui_edit_text_t* t1, const ui_edit_text_t* t2);\n    bool    (*copy_text)(const ui_edit_text_t* t, const ui_edit_range_t* range,\n                ui_edit_text_t* to);\n    void    (*copy)(const ui_edit_text_t* t, const ui_edit_range_t* range,\n                char* to, int32_t bytes);\n    bool    (*replace)(ui_edit_text_t* t, const ui_edit_range_t* r,\n                const ui_edit_text_t* text, ui_edit_to_do_t* undo_or_null);\n    bool    (*replace_utf8)(ui_edit_text_t* t, const ui_edit_range_t* r,\n                const char* utf8, int32_t bytes, ui_edit_to_do_t* undo_or_null);\n    void    (*dispose)(ui_edit_text_t* t);\n} ui_edit_text_if;\n\nextern ui_edit_text_if ui_edit_text;\n\ntypedef struct rt_begin_packed ui_edit_str_s {\n    char* u;    // always correct utf8 bytes not zero terminated(!) sequence\n    // s.g2b[s.g + 1] glyph to byte position inside s.u[]\n    // s.g2b[0] == 0, s.g2b[s.glyphs] == s.bytes\n    int32_t* g2b;  // g2b_0 or heap allocated glyphs to bytes indices\n    int32_t  b;    // number of bytes\n    int32_t  c;    // when capacity is zero .u is not heap allocated\n    int32_t  g;    // number of glyphs\n} rt_end_packed ui_edit_str_t;\n\ntypedef struct ui_edit_str_if {\n    bool (*init)(ui_edit_str_t* s, const char* utf8, int32_t bytes, bool heap);\n    void (*swap)(ui_edit_str_t* s1, ui_edit_str_t* s2);\n    int32_t (*gp_to_bp)(const char* s, int32_t bytes, int32_t gp); // or -1\n    int32_t (*bytes)(ui_edit_str_t* s, int32_t from, int32_t to); // glyphs\n    bool (*expand)(ui_edit_str_t* s, int32_t capacity); // reallocate\n    void (*shrink)(ui_edit_str_t* s); // get rid of extra heap memory\n    bool (*replace)(ui_edit_str_t* s, int32_t from, int32_t to, // glyphs\n                    const char* utf8, int32_t bytes); // [from..to[ exclusive\n    bool (*is_zwj)(uint32_t utf32); // zero width joiner\n    bool (*is_letter)(uint32_t utf32); // in European Alphabets\n    bool (*is_digit)(uint32_t utf32);\n    bool (*is_symbol)(uint32_t utf32);\n    bool (*is_alphanumeric)(uint32_t utf32);\n    bool (*is_blank)(uint32_t utf32); // white space\n    bool (*is_punctuation)(uint32_t utf32);\n    bool (*is_combining)(uint32_t utf32);\n    bool (*is_spacing)(uint32_t utf32); // spacing modifiers\n    bool (*is_cjk_or_emoji)(uint32_t utf32);\n    bool (*can_break)(uint32_t cp1, uint32_t cp2);\n    void (*test)(void);\n    void (*free)(ui_edit_str_t* s);\n    const ui_edit_str_t* const empty;\n} ui_edit_str_if;\n\nextern ui_edit_str_if ui_edit_str;\n\n/*\n    For caller convenience the bytes parameter in all calls can be set\n    to -1 for zero terminated utf8 strings which results in treating\n    strlen(utf8) as number of bytes.\n\n    ui_edit_str.init()\n            initializes not zero terminated utf8 string that may be\n            allocated on the heap or point out to an outside memory\n            location that should have longer lifetime and will be\n            treated as read only. init() may return false if\n            heap.alloc() returns null or the utf8 bytes sequence\n            is invalid.\n            s.b is number of bytes in the initialized string;\n            s.c is set to heap allocated capacity is set to zero\n            for strings that are not allocated on the heap;\n            s.g is number of the utf8 glyphs (aka Unicode codepoints)\n            in the string;\n            s.g2b[] is an array of s.g + 1 integers that maps glyph\n            positions to byte positions in the utf8 string. The last\n            element is number of bytes in the s.u memory.\n            Called must zero out the string struct before calling init().\n\n    ui_edit_str.bytes()\n            returns number of bytes in utf8 string in the exclusive\n            range [from..to[ between string glyphs.\n\n    ui_edit_str.replace()\n            replaces utf8 string in the exclusive range [from..to[\n            with the new utf8 string. The new string may be longer\n            or shorter than the replaced string. The function returns\n            false if the new string is invalid utf8 sequence or\n            heap allocation fails. The called must ensure that the\n            range [from..to[ is valid, failure to do so is a fatal\n            error. ui_edit_str.replace() moves string content to the heap.\n\n    ui_edit_str.free()\n            deallocates all heap allocated memory and zero out string\n            struct. It is incorrect to call free() on the string that\n            was not initialized or already freed.\n\n    All ui_edit_str_t keep \"precise\" number of utf8 bytes.\n    Caller may allocate extra byte and set it to 0x00\n    after retrieving and copying data from ui_edit_str if\n    the string content is intended to be used by any\n    other API that expects zero terminated strings.\n*/\n\n\nrt_end_c\n// ______________________________ ui_edit_view.h ______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n\nrt_begin_c\n\n// important ui_edit_view_t will refuse to layout into a box smaller than\n// width 3 x fm->em.w height 1 x fm->em.h\n\ntypedef struct ui_edit_view_s ui_edit_view_t;\n\ntypedef struct ui_edit_str_s ui_edit_str_t;\n\ntypedef struct ui_edit_doc_s ui_edit_doc_t;\n\ntypedef struct ui_edit_notify_s ui_edit_notify_t;\n\ntypedef struct ui_edit_to_do_s ui_edit_to_do_t;\n\ntypedef struct ui_edit_pr_s { // page/run coordinates\n    int32_t pn; // paragraph number\n    int32_t rn; // run number inside paragraph\n} ui_edit_pr_t;\n\ntypedef struct ui_edit_run_s {\n    int32_t bp;     // position in bytes  since start of the paragraph\n    int32_t gp;     // position in glyphs since start of the paragraph\n    int32_t bytes;  // number of bytes in this `run`\n    int32_t glyphs; // number of glyphs in this `run`\n    int32_t pixels; // width in pixels\n} ui_edit_run_t;\n\n// ui_edit_paragraph_t.initially text will point to readonly memory\n// with .allocated == 0; as text is modified it is copied to\n// heap and reallocated there.\n\ntypedef struct ui_edit_paragraph_s { // \"paragraph\" view consists of wrapped runs\n    int32_t runs;       // number of runs in this paragraph\n    ui_edit_run_t* run; // heap allocated array[runs]\n} ui_edit_paragraph_t;\n\ntypedef struct ui_edit_notify_view_s {\n    ui_edit_notify_t notify;\n    void*            that; // specific for listener\n    uintptr_t        data; // before -> after listener data\n} ui_edit_notify_view_t;\n\ntypedef struct ui_edit_view_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_edit_doc_t* doc; // document\n    ui_edit_notify_view_t listener;\n    ui_edit_range_t selection; // \"from\" selection[0] \"to\" selection[1]\n    ui_point_t caret; // (-1, -1) off\n    int32_t caret_width; // in pixels\n    ui_edit_pr_t scroll; // left top corner paragraph/run coordinates\n    int32_t last_x;    // last_x for up/down caret movement\n    ui_ltrb_t inside;  // inside insets space\n    struct {\n        int32_t w;       // inside.right - inside.left\n        int32_t h;       // inside.bottom - inside.top\n        int32_t buttons; // bit 0 and bit 1 for LEFT and RIGHT mouse buttons down\n    } edit;\n    // number of fully (not partially clipped) visible `runs' from top to bottom:\n    int32_t visible_runs;\n    // TODO: remove focused because it is the same as caret != (-1, -1)\n    bool focused;     // is focused and created caret\n    bool ro;          // Read Only\n    bool sle;         // Single Line Edit\n    bool hide_word_wrap; // do not paint word wrap\n    int32_t shown;    // debug: caret show/hide counter 0|1\n    // paragraphs memory:\n    ui_edit_paragraph_t* para; // para[e->doc->text.np]\n} ui_edit_view_t;\n\ntypedef struct ui_edit_view_if {\n    void (*init)(ui_edit_view_t* e, ui_edit_doc_t* d);\n    void (*set_font)(ui_edit_view_t* e, ui_fm_t* fm); // see notes below (*)\n    void (*move)(ui_edit_view_t* e, ui_edit_pg_t pg); // move caret clear selection\n    // replace selected text. If bytes < 0 text is treated as zero terminated\n    void (*replace)(ui_edit_view_t* e, const char* text, int32_t bytes);\n    // call save(e, null, &bytes) to retrieve number of utf8\n    // bytes required to save whole text including 0x00 terminating bytes\n    errno_t (*save)(ui_edit_view_t* e, char* text, int32_t* bytes);\n    void (*copy)(ui_edit_view_t* e);  // to clipboard\n    void (*cut)(ui_edit_view_t* e);   // to clipboard\n    // replace selected text with content of clipboard:\n    void (*paste)(ui_edit_view_t* e); // from clipboard\n    void (*select_all)(ui_edit_view_t* e); // select whole text\n    void (*erase)(ui_edit_view_t* e); // delete selected text\n    // keyboard actions dispatcher:\n    void (*key_down)(ui_edit_view_t* e);\n    void (*key_up)(ui_edit_view_t* e);\n    void (*key_left)(ui_edit_view_t* e);\n    void (*key_right)(ui_edit_view_t* e);\n    void (*key_page_up)(ui_edit_view_t* e);\n    void (*key_page_down)(ui_edit_view_t* e);\n    void (*key_home)(ui_edit_view_t* e);\n    void (*key_end)(ui_edit_view_t* e);\n    void (*key_delete)(ui_edit_view_t* e);\n    void (*key_backspace)(ui_edit_view_t* e);\n    void (*key_enter)(ui_edit_view_t* e);\n    // called when ENTER keyboard key is pressed in single line mode\n    void (*enter)(ui_edit_view_t* e);\n    // fuzzer test:\n    void (*fuzz)(ui_edit_view_t* e);      // start/stop fuzzing test\n    void (*dispose)(ui_edit_view_t* e);\n} ui_edit_view_if;\n\nextern ui_edit_view_if ui_edit_view;\n\n/*\n    Notes:\n    set_font()\n        neither edit.view.font = font nor measure()/layout() functions\n        do NOT dispose paragraphs layout unless geometry changed because\n        it is quite expensive operation. But choosing different font\n        on the fly needs to re-layout all paragraphs. Thus caller needs\n        to set font via this function instead which also requests\n        edit UI element re-layout.\n\n    .ro\n        readonly edit->ro is used to control readonly mode.\n        If edit control is readonly its appearance does not change but it\n        refuses to accept any changes to the rendered text.\n\n    .wb\n        wordbreak this attribute was removed as poor UX human experience\n        along with single line scroll editing. See note below about .sle.\n\n    .sle\n        single line edit control.\n        Edit UI element does NOT support horizontal scroll and breaking\n        words semantics as it is poor UX human experience. This is not\n        how humans (apart of software developers) edit text.\n        If content of the edit UI element is wider than the bounding box\n        width the content is broken on word boundaries and vertical scrolling\n        semantics is supported. Layouts containing edit control of the single\n        line height are strongly encouraged to enlarge edit control layout\n        vertically on as needed basis similar to Google Search Box behavior\n        change implemented in 2023.\n        If multiline is set to true by the callers code the edit UI layout\n        snaps text to the top of x,y,w,h box otherwise the vertical space\n        is distributed evenly between single line of text and top bottom\n        margins.\n        IMPORTANT: SLE resizes itself vertically to accommodate for\n        input that is too wide. If caller wants to limit vertical space it\n        will need to hook .measure() function of SLE and do the math there.\n*/\n\n/*\n    For caller convenience the bytes parameter in all calls can be set\n    to -1 for zero terminated utf8 strings which results in treating\n    strlen(utf8) as number of bytes.\n\n    ui_edit_str.init()\n            initializes not zero terminated utf8 string that may be\n            allocated on the heap or point out to an outside memory\n            location that should have longer lifetime and will be\n            treated as read only. init() may return false if\n            heap.alloc() returns null or the utf8 bytes sequence\n            is invalid.\n            s.b is number of bytes in the initialized string;\n            s.c is set to heap allocated capacity is set to zero\n            for strings that are not allocated on the heap;\n            s.g is number of the utf8 glyphs (aka Unicode codepoints)\n            in the string;\n            s.g2b[] is an array of s.g + 1 integers that maps glyph\n            positions to byte positions in the utf8 string. The last\n            element is number of bytes in the s.u memory.\n            Called must zero out the string struct before calling init().\n\n    ui_edit_str.bytes()\n            returns number of bytes in utf8 string in the exclusive\n            range [from..to[ between string glyphs.\n\n    ui_edit_str.replace()\n            replaces utf8 string in the exclusive range [from..to[\n            with the new utf8 string. The new string may be longer\n            or shorter than the replaced string. The function returns\n            false if the new string is invalid utf8 sequence or\n            heap allocation fails. The called must ensure that the\n            range [from..to[ is valid, failure to do so is a fatal\n            error. ui_edit_str.replace() moves string content to the heap.\n\n    ui_edit_str.free()\n            deallocates all heap allocated memory and zero out string\n            struct. It is incorrect to call free() on the string that\n            was not initialized or already freed.\n\n    All ui_edit_str_t keep \"precise\" number of utf8 bytes.\n    Caller may allocate extra byte and set it to 0x00\n    after retrieving and copying data from ui_edit_str if\n    the string content is intended to be used by any\n    other API that expects zero terminated strings.\n*/\n\nrt_end_c\n\n// ________________________________ ui_label.h ________________________________\n\nrt_begin_c\n\ntypedef ui_view_t ui_label_t;\n\nvoid ui_view_init_label(ui_view_t* v);\n\n// label insets and padding left/right are intentionally\n// smaller than button/slider/toggle controls\n\n#define ui_label(min_width_em, s) {                    \\\n    .type = ui_view_label, .init = ui_view_init_label, \\\n    .fm = &ui_app.fm.prop.normal,                      \\\n    .p.text = s,                                       \\\n    .min_w_em = min_width_em, .min_h_em = 1.25f,       \\\n    .insets  = {                                       \\\n        .left  = ui_view_i_lr, .top    = ui_view_i_tb, \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb  \\\n    },                                                 \\\n    .padding = {                                       \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb, \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb, \\\n    }                                                  \\\n}\n\n// text with \"&\" keyboard shortcuts:\n\nvoid ui_label_init(ui_label_t* t, fp32_t min_w_em, const char* format, ...);\nvoid ui_label_init_va(ui_label_t* t, fp32_t min_w_em, const char* format, va_list va);\n\n// use this macro for initialization:\n//    ui_label_t label = ui_label(min_width_em, s);\n// or:\n//    label = (ui_label_t)ui_label(min_width_em, s);\n// which is subtle C difference of constant and\n// variable initialization and I did not find universal way\n\nrt_end_c\n\n// _______________________________ ui_button.h ________________________________\n\nrt_begin_c\n\ntypedef ui_view_t ui_button_t;\n\nvoid ui_view_init_button(ui_view_t* v);\n\nvoid ui_button_init(ui_button_t* b, const char* label, fp32_t min_width_em,\n    void (*callback)(ui_button_t* b));\n\n// ui_button_clicked can only be used on static button variables\n\n#define ui_button_clicked(name, s, min_width_em, ...)       \\\n    static void name ## _clicked(ui_button_t* name) {       \\\n        (void)name; /* no warning if unused */              \\\n        { __VA_ARGS__ }                                     \\\n    }                                                       \\\n    static                                                  \\\n    ui_button_t name = {                                    \\\n        .type = ui_view_button,                             \\\n        .init = ui_view_init_button,                        \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .p.text = s,                                        \\\n        .callback = name ## _clicked,                       \\\n        .color_id = ui_color_id_button_text,                \\\n        .min_w_em = min_width_em, .min_h_em = 1.25f,        \\\n        .insets  = {                                        \\\n            .left  = ui_view_i_lr, .top    = ui_view_i_tb,  \\\n            .right = ui_view_i_lr, .bottom = ui_view_i_tb   \\\n        },                                                  \\\n        .padding = {                                        \\\n            .left  = ui_view_p_lr, .top    = ui_view_p_tb,  \\\n            .right = ui_view_p_lr, .bottom = ui_view_p_tb,  \\\n        }                                                   \\\n    }\n\n#define ui_button(s, min_width_em, clicked) {               \\\n    .type = ui_view_button,                                 \\\n    .init = ui_view_init_button,                            \\\n    .fm = &ui_app.fm.prop.normal,                           \\\n    .p.text = s,                                            \\\n    .callback = clicked,                                    \\\n    .color_id = ui_color_id_button_text,                    \\\n    .min_w_em = min_width_em, .min_h_em = 1.25f,            \\\n    .insets  = {                                            \\\n        .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n    },                                                      \\\n    .padding = {                                            \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n    }                                                       \\\n}\n\n// usage:\n//\n// ui_button_clicked(button, \"&Button\", 7.0, {\n//      if (button->state.pressed) {\n//          // do something on click that happens on release mouse button\n//      }\n// })\n//\n// or:\n//\n// static void button_flipped(ui_button_t* b) {\n//      swear(b->flip == true); // 2 state button, clicked on mouse press button\n//      if (b->state.pressed) {\n//          // show something:\n//      } else {\n//          // show something else:\n//      }\n// }\n//\n// ui_button_t button = ui_button(7.0, \"&Button\", button_flipped);\n//\n// or\n//\n// ui_button_t button = ui_view)button(button);\n// ui_view.set_text(button.text, \"&Button\");\n// button.min_w_em = 7.0;\n// button.callback = button_flipped;\n//\n// Note:\n// ui_button_clicked(button, \"&Button\", 7.0, {\n//      button->state.pressed = !button->state.pressed;\n//      // is similar to: button.flip = true but it leads thru\n//      // multiple button paint and click happens on mouse button\n//      // release not press\n// }\n\n\nrt_end_c\n\n// ________________________________ ui_image.h ________________________________\n\nrt_begin_c\n\n// \"image view\"\n\n// To enable zoom/pan make view focusable:\n// iv.focusable = true;\n\n// Field .image may have .pixels pointer and .bitmap == null.\n// If this is the case the direct pixels transfer to the\n// device is used. RGBA bitmaps must be allocated on the\n// device otherwise ui_gdi.rgbx() call is used and alpha\n// is ignored.\n\ntypedef struct ui_image_s ui_image_t;\n\ntypedef struct ui_image_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_bitmap_t image; // view does NOT own or dispose image->bitmap\n    fp64_t     alpha; // for rgba images\n    // actual scale() is: z = 2 ^ (zn - 1) / 2 ^ (zd - 1)\n    int32_t zoom; // 0..8\n    // 0=16:1 1=8:1 2=4:1 3=2:1 4=1:1 5=1:2 6=1:4 7=1:8 8=1:16\n    int32_t zn; // zoom nominator (1, 2, 3, ...)\n    int32_t zd; // zoom denominator (1, 2, 3, ...)\n    fp64_t  sx; // shift x [0..1.0] in view coordinates\n    fp64_t  sy; // shift y [0..1.0]\n    struct { // only visible when focused\n        ui_view_t   bar; // ui_view(span) {zoom in, zoom 1:1, zoom out, help}\n        ui_button_t copy; // copy image to clipboard\n        ui_button_t zoom_in;\n        ui_button_t zoom_1t1; // 1:1\n        ui_button_t zoom_out;\n        ui_button_t fit;\n        ui_button_t fill;\n        ui_button_t help;\n        ui_label_t  ratio;\n    } tool;\n    ui_point_t drag_start;\n    fp64_t when; // to hide toolbar\n    bool fit;    // best fit into view\n    bool fill;   // fill entire view\n    // fit and fill cannot be true at the same time\n    // when fit: false and fill: false the zoom ratio is in effect\n} ui_image_t;\n\ntypedef struct ui_image_if {\n    void      (*init)(ui_image_t* iv);\n    void      (*init_with)(ui_image_t* iv, const uint8_t* pixels,\n                           int32_t width, int32_t height,\n                           int32_t bpp, int32_t stride);\n    // ration can only be: 16:1 8:1 4:1 2:1 1:1 1:2 1:4 1:8 1:16\n    // but ignored if .fit or .fill is true\n    void      (*ratio)(ui_image_t* iv, int32_t nominator, int32_t denominator);\n    fp64_t    (*scale)(ui_image_t* iv); // 2 ^ (zn - 1) / 2 ^ (zd - 1)\n    ui_rect_t (*position)(ui_image_t* iv);\n} ui_image_if;\n\nextern ui_image_if ui_image;\n\nrt_end_c\n\n// ________________________________ ui_midi.h _________________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include <stdint.h>\n#include <errno.h>\n\n#ifdef __cplusplus\n    extern \"C\" {\n#endif\n\ntypedef struct ui_midi_s ui_midi_t;\n\ntypedef struct ui_midi_s {\n    uint8_t data[16 * 8]; // opaque implementation data\n    // must return 0 if successful or error otherwise:\n    int64_t (*notify)(ui_midi_t* midi, int64_t flags);\n} ui_midi_t;\n\ntypedef struct {\n    // flags bitset:\n    int32_t const success; // when the clip is done playing\n    int32_t const failure; // on error playing media\n    int32_t const aborted; // on stop() call\n    int32_t const superseded;\n    // midi has it's own section of legacy error messages\n    void    (*error)(errno_t r, char* s, int32_t count);\n    errno_t (*open)(ui_midi_t* midi, const char* filename);\n    errno_t (*play)(ui_midi_t* midi);\n    errno_t (*rewind)(ui_midi_t* midi);\n    errno_t (*stop)(ui_midi_t* midi);\n    errno_t (*get_volume)(ui_midi_t* midi, fp64_t *volume);\n    errno_t (*set_volume)(ui_midi_t* midi, fp64_t  volume);\n    bool    (*is_open)(ui_midi_t* midi);\n    bool    (*is_playing)(ui_midi_t* midi);\n    void    (*close)(ui_midi_t* midi);\n} ui_midi_if;\n\nextern ui_midi_if ui_midi;\n\n\n/*\n    success:\n    \"The conditions initiating the callback function have been met.\"\n    I guess meaning media is done playing...\n\n    failure:\n    \"A device error occurred while the device was executing the command.\"\n\n    aborted:\n    \"The device received a command that prevented the current\n    conditions for initiating the callback function from\n    being met. If a new command interrupts the current command\n    and it also requests notification, the device sends this\n    message only and not `superseded`\".\n    I guess meaning media is stopped playing...\n\n    superseded:\n    \"The device received another command with the \"notify\" flag set\n     and the current conditions for initiating the callback function\n     have been superseded.\"\n*/\n\n#ifdef __cplusplus\n}\n#endif\n\n\n\n\n// _______________________________ ui_slider.h ________________________________\n\nrt_begin_c\n\ntypedef struct ui_slider_s ui_slider_t;\n\ntypedef struct ui_slider_s {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    int32_t step;\n    fp64_t time; // time last button was pressed\n    ui_wh_t wh;  // text measurement (special case for %0*d)\n    ui_button_t inc; // can be hidden\n    ui_button_t dec; // can be hidden\n    int32_t value;  // for ui_slider_t range slider control\n    int32_t value_min;\n    int32_t value_max;\n    // style:\n    bool notched; // true if marked with a notches and has a thumb\n} ui_slider_t;\n\nvoid ui_view_init_slider(ui_view_t* v);\n\nvoid ui_slider_init(ui_slider_t* r, const char* label, fp32_t min_w_em,\n    int32_t value_min, int32_t value_max, void (*callback)(ui_view_t* r));\n\n// ui_slider_changed can only be used on static slider variables\n\n#define ui_slider_changed(name, s, min_width_em, mn,  mx, fmt, ...) \\\n    static void name ## _changed(ui_slider_t* name) {               \\\n        (void)name; /* no warning if unused */                      \\\n        { __VA_ARGS__ }                                             \\\n    }                                                               \\\n    static                                                          \\\n    ui_slider_t name = {                                            \\\n        .view = {                                                   \\\n            .type = ui_view_slider,                                 \\\n            .init = ui_view_init_slider,                            \\\n            .fm = &ui_app.fm.prop.normal,                           \\\n            .p.text = s,                                            \\\n            .format = fmt,                                          \\\n            .callback = name ## _changed,                           \\\n            .min_w_em = min_width_em, .min_h_em = 1.25f,            \\\n            .insets  = {                                            \\\n                .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n                .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n            },                                                      \\\n            .padding = {                                            \\\n                .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n                .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n            }                                                       \\\n        },                                                          \\\n        .value_min = mn, .value_max = mx, .value = mn,              \\\n    }\n\n#define ui_slider(s, min_width_em, mn, mx, fmt, changed) {          \\\n    .view = {                                                       \\\n        .type = ui_view_slider,                                     \\\n        .init = ui_view_init_slider,                                \\\n        .fm = &ui_app.fm.prop.normal,                               \\\n        .p.text = s,                                                \\\n        .callback = changed,                                        \\\n        .format = fmt,                                              \\\n        .min_w_em = min_width_em, .min_h_em = 1.25f,                \\\n            .insets  = {                                            \\\n                .left  = ui_view_i_lr, .top    = ui_view_i_tb,      \\\n                .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n            },                                                      \\\n            .padding = {                                            \\\n                .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n                .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n            }                                                       \\\n    },                                                              \\\n    .value_min = mn, .value_max = mx, .value = mn,                  \\\n}\n\nrt_end_c\n// ________________________________ ui_theme.h ________________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n\nrt_begin_c\n\nenum {\n    ui_theme_app_mode_default     = 0,\n    ui_theme_app_mode_allow_dark  = 1,\n    ui_theme_app_mode_force_dark  = 2,\n    ui_theme_app_mode_force_light = 3\n};\n\ntypedef struct  {\n    bool (*is_app_dark)(void);\n    bool (*is_system_dark)(void);\n    bool (*are_apps_dark)(void);\n    void (*set_preferred_app_mode)(int32_t mode);\n    void (*flush_menu_themes)(void);\n    void (*allow_dark_mode_for_app)(bool allow);\n    void (*allow_dark_mode_for_window)(bool allow);\n    void (*refresh)(void);\n    void (*test)(void);\n} ui_theme_if;\n\nextern ui_theme_if ui_theme;\n\nrt_end_c\n\n// _______________________________ ui_toggle.h ________________________________\n\nrt_begin_c\n\ntypedef ui_view_t ui_toggle_t;\n\n// label may contain \"___\" which will be replaced with \"On\" / \"Off\"\nvoid ui_toggle_init(ui_toggle_t* b, const char* label, fp32_t ems,\n    void (*callback)(ui_toggle_t* b));\n\nvoid ui_view_init_toggle(ui_view_t* v);\n\n// ui_toggle_on_off can only be used on static toggle variables\n\n#define ui_toggle_on_off(name, s, min_width_em, ...)        \\\n    static void name ## _on_off(ui_toggle_t* name) {        \\\n        (void)name; /* no warning if unused */              \\\n        { __VA_ARGS__ }                                     \\\n    }                                                       \\\n    static                                                  \\\n    ui_toggle_t name = {                                    \\\n        .type = ui_view_toggle,                             \\\n        .init = ui_view_init_toggle,                        \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .min_w_em = min_width_em,  .min_h_em = 1.25f,       \\\n        .p.text = s,                                        \\\n        .callback = name ## _on_off,                        \\\n        .insets  = {                                        \\\n            .left  = 1.75f,        .top    = ui_view_i_tb,  \\\n            .right = ui_view_i_lr, .bottom = ui_view_i_tb   \\\n        },                                                  \\\n        .padding = {                                        \\\n            .left  = ui_view_p_lr, .top    = ui_view_p_tb,  \\\n            .right = ui_view_p_lr, .bottom = ui_view_p_tb,  \\\n        }                                                   \\\n    }\n\n#define ui_toggle(s, min_width_em, on_off) {                \\\n    .type = ui_view_toggle,                                 \\\n    .init = ui_view_init_toggle,                            \\\n    .fm = &ui_app.fm.prop.normal,                           \\\n    .p.text = s,                                            \\\n    .callback = on_off,                                     \\\n    .min_w_em = min_width_em,  .min_h_em = 1.25f,           \\\n    .insets  = {                                            \\\n        .left  = 1.75f,        .top    = ui_view_i_tb,      \\\n        .right = ui_view_i_lr, .bottom = ui_view_i_tb       \\\n    },                                                      \\\n    .padding = {                                            \\\n        .left  = ui_view_p_lr, .top    = ui_view_p_tb,      \\\n        .right = ui_view_p_lr, .bottom = ui_view_p_tb,      \\\n    }                                                       \\\n}\n\nrt_end_c\n\n// _________________________________ ui_mbx.h _________________________________\n\nrt_begin_c\n\n// Options like:\n//   \"Yes\"|\"No\"|\"Abort\"|\"Retry\"|\"Ignore\"|\"Cancel\"|\"Try\"|\"Continue\"\n// maximum number of choices presentable to human is 4.\n\ntypedef struct {\n    union {\n        ui_view_t view;\n        struct ui_view_s;\n    };\n    ui_label_t   label;\n    ui_button_t  button[4];\n    int32_t      option; // -1 or option chosen by user\n    const char** options;\n} ui_mbx_t;\n\nvoid ui_view_init_mbx(ui_view_t* v);\n\nvoid ui_mbx_init(ui_mbx_t* mx, const char* option[], const char* format, ...);\n\n// ui_mbx_on_choice can only be used on static mbx variables\n\n\n#define ui_mbx_chosen(name, s, code, ...)                        \\\n                                                                 \\\n    static char* name ## _options[] = { __VA_ARGS__, null };     \\\n                                                                 \\\n    static void name ## _chosen(ui_mbx_t* m, int32_t option) {   \\\n        (void)m; (void)option; /* no warnings if unused */       \\\n        code                                                     \\\n    }                                                            \\\n    static                                                       \\\n    ui_mbx_t name = {                                            \\\n        .view = {                                                \\\n            .type = ui_view_mbx,                                 \\\n            .init = ui_view_init_mbx,                            \\\n            .fm = &ui_app.fm.prop.normal,                        \\\n            .p.text = s,                                         \\\n            .callback = name ## _chosen,                         \\\n            .padding = { .left  = 0.125, .top    = 0.25,         \\\n                         .right = 0.125, .bottom = 0.25 },       \\\n            .insets  = { .left  = 0.125, .top    = 0.25,         \\\n                         .right = 0.125, .bottom = 0.25 }        \\\n        },                                                       \\\n        .options = name ## _options                              \\\n    }\n\n#define ui_mbx(s, chosen, ...) {                            \\\n    .view = {                                               \\\n        .type = ui_view_mbx, .init = ui_view_init_mbx,      \\\n        .fm = &ui_app.fm.prop.normal,                       \\\n        .p.text = s,                                        \\\n        .callback = chosen,                                 \\\n        .padding = { .left  = 0.125, .top    = 0.25,        \\\n                     .right = 0.125, .bottom = 0.25 },      \\\n        .insets  = { .left  = 0.125, .top    = 0.25,        \\\n                     .right = 0.125, .bottom = 0.25 }       \\\n    },                                                      \\\n    .options = (const char*[]){ __VA_ARGS__, null },        \\\n}\n\nrt_end_c\n// _______________________________ ui_caption.h _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n\nrt_begin_c\n\ntypedef struct ui_caption_s {\n    ui_view_t view;\n    // caption`s children:\n    ui_button_t icon;\n    ui_label_t title;\n    ui_view_t spacer;\n    ui_button_t menu; // use: ui_caption.button_menu.cb := your callback\n    ui_button_t mode; // switch between dark/light mode\n    ui_button_t mini;\n    ui_button_t maxi;\n    ui_button_t full;\n    ui_button_t quit;\n} ui_caption_t;\n\nextern ui_caption_t ui_caption;\n\nrt_end_c\n\n// _________________________________ ui_app.h _________________________________\n\nrt_begin_c\n\n// link.exe /SUBSYSTEM:WINDOWS single window application\n\ntypedef struct ui_app_message_handler_s ui_app_message_handler_t;\n\ntypedef struct ui_app_message_handler_s {\n    void* that;\n    ui_app_message_handler_t* next;\n    bool (*callback)(ui_app_message_handler_t* handler, int32_t m,\n                     int64_t wp, int64_t lp, int64_t* rt);\n} ui_app_message_handler_t;\n\ntypedef struct ui_dpi_s { // max(dpi_x, dpi_y)\n    int32_t system;  // system dpi\n    int32_t process; // process dpi\n    // 15\" diagonal monitor 3840x2160 175% scaled\n    // monitor dpi effective 168, angular 248 raw 284\n    int32_t monitor_effective; // effective with regard of user scaling\n    int32_t monitor_raw;       // with regard of physical screen size\n    int32_t monitor_angular;   // diagonal raw\n    int32_t monitor_max;       // maximum of effective,raw,angular\n    int32_t window;            // main window dpi\n} ui_dpi_t;\n\n// in inches (because monitors customary are)\n// it is not in points (1/72 inch) like font size\n// because it is awkward to express large area\n// size in typography measurements.\n\ntypedef struct ui_window_sizing_s {\n    fp32_t ini_w; // initial window width in inches\n    fp32_t ini_h; // 0,0 means set to min_w, min_h\n    fp32_t min_w; // minimum window width in inches\n    fp32_t min_h; // 0,0 means - do not care use content size\n    fp32_t max_w; // maximum window width in inches\n    fp32_t max_h; // 0,0 means as big as user wants\n    // \"sizing\" \"estimate or measure something's dimensions.\"\n\t// initial window sizing only used on the first invocation\n\t// actual user sizing is stored in the configuration and used\n\t// on all launches except the very first.\n} ui_window_sizing_t;\n\ntypedef struct ui_fms_s {\n    // when font handles are re-created on system scaling change\n    // metrics \"em\" and font geometry filled\n    ui_fm_t normal; // regular UI font ~ 11-12pt\n    ui_fm_t tiny;   // small UI font ~ 8pt\n    ui_fm_t title;  // Largest Title font\n    ui_fm_t rubric; // Subtitle font\n    ui_fm_t H1;     // bolder header font\n    ui_fm_t H2;\n    ui_fm_t H3;\n} ui_fms_t;\n\ntypedef struct { // TODO: split to ui_app_t and ui_app_if, move data after methods\n    // implemented by client:\n    const char* class_name;\n    // called before creating main window\n    void (*init)(void);\n    // called instead of init() for console apps and when .no_ui=true\n    int (*main)(void);\n    // class_name and init must be set before main()\n    void (*opened)(void);      // window has been created and shown\n    void (*every_sec)(void);   // if not null called ~ once a second\n    void (*every_100ms)(void); // called ~10 times per second\n    // .can_close() called before window is closed and can be\n    // used in a meaning of .closing()\n    bool (*can_close)(void);   // window can be closed\n    void (*closed)(void);      // window has been closed\n    void (*fini)(void);        // called before WinMain() return\n    // must be filled by application:\n    const char* title;\n    ui_window_sizing_t const window_sizing;\n    // TODO: struct {} visibility;\n    // see: ui.visibility.*\n    int32_t visibility;         // initial window_visibility state\n    int32_t last_visibility;    // last window_visibility state from last run\n    int32_t startup_visibility; // window_visibility from parent process\n    ui_canvas_t canvas;  // set by message.paint\n    // ui flags:\n    bool is_full_screen;\n    bool no_ui;      // do not create application window at all\n    bool dark_mode;  // forced dark  mode for the whole application\n    bool light_mode; // forced light mode for the whole application\n    bool no_decor;   // window w/o title bar, min/max close buttons\n    bool no_min;     // window w/o minimize button on title bar and sys menu\n    bool no_max;     // window w/o maximize button on title bar\n    bool no_size;    // window w/o maximize button on title bar\n    bool no_clip;    // allows to resize window above hosting monitor size\n    bool hide_on_minimize; // like task manager minimize means hide\n    ui_window_t window;\n    ui_icon_t icon; // may be null\n    uint64_t  tid; // main thread id\n    int32_t   exit_code; // application exit code\n    ui_dpi_t  dpi;\n    ui_rect_t wrc;  // window rectangle including non-client area\n    ui_rect_t crc;  // client rectangle\n    ui_rect_t mrc;  // monitor rectangle\n    ui_rect_t prc;  // previously invalidated paint rectangle inside crc\n    ui_rect_t work_area; // current monitor work area\n    int32_t   caption_height; // caption height\n    ui_wh_t   border;    // frame border size\n    // not to call rt_clock.seconds() too often:\n    fp64_t     now;  // ssb \"seconds since boot\" updated on each message\n    ui_view_t* root; // show_window() changes ui.hidden\n    ui_view_t* content;\n    ui_view_t* caption;\n    ui_view_t* focus; // does not affect message routing\n    struct { // font metrics and handles\n        ui_fms_t prop;  // proportional fonts\n        ui_fms_t mono;  // monospaced fonts\n    } fm;\n    // TODO: struct {} keyboard\n    // keyboard state now:\n    bool alt;\n    bool ctrl;\n    bool shift;\n    // TODO: struct {} mouse\n    // mouse buttons state\n    bool mouse_swapped;\n    bool mouse_left;   // left or if buttons are swapped - right button pressed\n    bool mouse_middle; // rarely useful\n    bool mouse_right;  // context button pressed\n    ui_point_t mouse; // mouse/touchpad pointer\n    ui_cursor_t cursor; // current cursor\n    struct {\n        ui_cursor_t arrow;\n        ui_cursor_t wait;\n        ui_cursor_t ibeam;\n        ui_cursor_t size_nwse; // north west - south east\n        ui_cursor_t size_nesw; // north east - south west\n        ui_cursor_t size_we;   // west - east\n        ui_cursor_t size_ns;   // north - south\n        ui_cursor_t size_all;  // north - south\n    } cursors;\n    struct { // animated_groot state\n        ui_view_t* view;\n        ui_view_t* focused; // focused view before animated_groot started\n        int32_t step;\n        fp64_t time; // closing time or zero\n        int32_t x; // (x,y) for tooltip (-1,y) for toast\n        int32_t y; // screen coordinates for tooltip\n    } animating;\n    ui_app_message_handler_t* handlers;\n    // post(..., delay_in_seconds, ...) can be scheduled from any thread executed\n    // on UI thread\n    void (*post)(rt_work_t* work); // work.when == 0 meaning ASAP\n    void (*request_redraw)(void);  // very fast <2 microseconds\n    void (*draw)(void); // paint window now - bad idea do not use\n    // inch to pixels and reverse translation via ui_app.dpi.window\n    fp32_t  (*px2in)(int32_t pixels);\n    int32_t (*in2px)(fp32_t inches);\n    errno_t (*set_layered_window)(ui_color_t color, float alpha);\n    bool (*is_active)(void); // is application window active\n    bool (*is_minimized)(void);\n    bool (*is_maximized)(void);\n    bool (*focused)(void); // application window has keyboard focus\n    void (*activate)(void); // request application window activation\n    void (*set_title)(const char* title);\n    void (*capture_mouse)(bool on); // capture mouse global input on/of\n    void (*move_and_resize)(const ui_rect_t* rc);\n    void (*bring_to_foreground)(void); // not necessary topmost\n    void (*make_topmost)(void);   // in foreground hierarchy of windows\n    void (*request_focus)(void);  // request application window keyboard focus\n    void (*bring_to_front)(void); // activate() + bring_to_foreground() +\n                                  // make_topmost() + request_focus()\n    // measure and layout:\n    void (*request_layout)(void); // requests layout on UI tree before paint()\n    void (*invalidate)(const ui_rect_t* rc);\n    void (*full_screen)(bool on);\n    void (*set_cursor)(ui_cursor_t c);\n    void (*close)(void); // attempts to close (can_close() permitting)\n    // forced quit() even if can_close() returns false\n    void (*quit)(int32_t ec);  // ui_app.exit_code = ec; PostQuitMessage(ec);\n    ui_timer_t (*set_timer)(uintptr_t id, int32_t milliseconds); // see notes\n    void (*kill_timer)(ui_timer_t id);\n    void (*show_window)(int32_t show); // see show_window enum\n    void (*show_toast)(ui_view_t* toast, fp64_t seconds); // toast(null) to cancel\n    void (*show_hint)(ui_view_t* tooltip, int32_t x, int32_t y, fp64_t seconds);\n    void (*toast_va)(fp64_t seconds, const char* format, va_list va);\n    void (*toast)(fp64_t seconds, const char* format, ...);\n    // caret calls must be balanced by caller\n    void (*create_caret)(int32_t w, int32_t h);\n    void (*show_caret)(void);\n    void (*move_caret)(int32_t x, int32_t y);\n    void (*hide_caret)(void);\n    void (*destroy_caret)(void);\n    // beep sounds:\n    void (*beep)(int32_t kind);\n    // registry interface:\n    void (*data_save)(const char* name, const void* data, int32_t bytes);\n    int32_t (*data_size)(const char* name);\n    int32_t (*data_load)(const char* name, void* data, int32_t bytes); // returns bytes read\n    // filename dialog:\n    // const char* filter[] =\n    //     {\"Text Files\", \".txt;.doc;.ini\",\n    //      \"Executables\", \".exe\",\n    //      \"All Files\", \"*\"};\n    // const char* fn = ui_app.open_filename(\"C:\\\\\", filter, rt_countof(filter));\n    const char* (*open_file)(const char* folder, const char* filter[], int32_t n);\n    bool (*is_stdout_redirected)(void);\n    bool (*is_console_visible)(void);\n    int  (*console_attach)(void); // attempts to attach to parent terminal\n    int  (*console_create)(void); // allocates new console\n    void (*console_show)(bool b);\n    // stats:\n    int32_t paint_count; // number of paint calls\n    fp64_t paint_time; // last paint duration in seconds\n    fp64_t paint_max;  // max of last 128 paint\n    fp64_t paint_avg;  // EMA of last 128 paints\n    fp64_t paint_fps;  // EMA of last 128 paints\n    fp64_t paint_last; // rt_clock.seconds() of last paint\n    fp64_t paint_dt_min; // minimum time between 2 paints\n} ui_app_t;\n\nextern ui_app_t ui_app;\n\nrt_end_c\n\nrt_begin_c\n\n// https://en.wikipedia.org/wiki/Fuzzing\n// aka \"Monkey\" testing\n\ntypedef struct ui_fuzzing_s {\n    rt_work_t    base;\n    const char*  utf8; // .character(utf8)\n    int32_t      key;  // .key_pressed(key)/.key_released(key)\n    ui_point_t*  pt;   // .move_move()\n    // key_press and character\n    bool         alt;\n    bool         ctrl;\n    bool         shift;\n    // mouse modifiers\n    bool         left; // tap() buttons:\n    bool         right;\n    bool         double_tap;\n    bool         long_press;\n    // custom\n    int32_t      op;\n    void*        data;\n} ui_fuzzing_t;\n\ntypedef struct ui_fuzzing_if {\n    void (*start)(uint32_t seed);\n    bool (*is_running)(void);\n    bool (*from_inside)(void); // true if called originated inside fuzzing\n    void (*next_random)(ui_fuzzing_t* f); // called if `next` is null\n    void (*dispatch)(ui_fuzzing_t* f);    // dispatch work\n    // next() called instead of random if not null\n    void (*next)(ui_fuzzing_t* f);\n    // custom() called instead of dispatch() if not null\n    void (*custom)(ui_fuzzing_t* f);\n    void (*stop)(void);\n} ui_fuzzing_if;\n\nextern ui_fuzzing_if ui_fuzzing;\n\nrt_end_c\n\n\n#endif // ui_definition\n\n#ifdef ui_implementation\n// _________________________________ ui_app.c _________________________________\n\n#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n#pragma push_macro(\"ui_app_window\")\n#pragma push_macro(\"ui_app_canvas\")\n\nstatic bool ui_app_trace_utf16_keyboard_input;\n\n#define ui_app_window() ((HWND)ui_app.window)\n#define ui_app_canvas() ((HDC)ui_app.canvas)\n\nstatic WNDCLASSW ui_app_wc; // window class\n\nstatic NONCLIENTMETRICSW ui_app_ncm = { sizeof(NONCLIENTMETRICSW) };\nstatic MONITORINFO ui_app_mi = {sizeof(MONITORINFO)};\n\nstatic rt_event_t ui_app_event_quit;\nstatic rt_event_t ui_app_event_invalidate;\nstatic rt_event_t ui_app_wt; // waitable timer;\n\nstatic rt_work_queue_t ui_app_queue;\n\nstatic uintptr_t ui_app_timer_1s_id;\nstatic uintptr_t ui_app_timer_100ms_id;\n\nstatic bool ui_app_layout_dirty; // call layout() before paint\n\nstatic char ui_app_decoded_pressed[16];  // utf8 of last decoded pressed key\nstatic char ui_app_decoded_released[16]; // utf8 of last decoded released key\nstatic uint16_t ui_app_high_surrogate;\n\ntypedef void (*ui_app_animate_function_t)(int32_t step);\n\nstatic struct {\n    ui_app_animate_function_t f;\n    int32_t count;\n    int32_t step;\n    ui_timer_t timer;\n} ui_app_animate;\n\n// Animation timer is Windows minimum of 10ms, but in reality the timer\n// messages are far from isochronous and more likely to arrive at 16 or\n// 32ms intervals and can be delayed.\n\nstatic void ui_app_post_message(int32_t m, int64_t wp, int64_t lp) {\n    rt_fatal_win32err(PostMessageA(ui_app_window(), (UINT)m,\n            (WPARAM)wp, (LPARAM)lp));\n}\n\nstatic void ui_app_update_wt_timeout(void) {\n    fp64_t next_due_at = -1.0;\n    rt_atomics.spinlock_acquire(&ui_app_queue.lock);\n    if (ui_app_queue.head != null) {\n        next_due_at = ui_app_queue.head->when;\n    }\n    rt_atomics.spinlock_release(&ui_app_queue.lock);\n    if (next_due_at >= 0) {\n        static fp64_t last_next_due_at;\n        fp64_t dt = next_due_at - rt_clock.seconds();\n        if (dt <= 0) {\n            ui_app_post_message(WM_NULL, 0, 0);\n        } else if (last_next_due_at != next_due_at) {\n            // Negative values indicate relative time in 100ns intervals\n            LARGE_INTEGER rt = {0}; // relative negative time\n            rt.QuadPart = (LONGLONG)(-dt * 1.0E+7);\n            rt_swear(rt.QuadPart < 0, \"dt: %.6f %lld\", dt, rt.QuadPart);\n            rt_fatal_win32err(\n                SetWaitableTimer(ui_app_wt, &rt, 0, null, null, 0)\n            );\n        }\n        last_next_due_at = next_due_at;\n    }\n}\n\nstatic void ui_app_post(rt_work_t* w) {\n    if (w->queue == null) { w->queue = &ui_app_queue; }\n    // work item can be reused but only with the same queue\n    rt_assert(w->queue == &ui_app_queue);\n    rt_work_queue.post(w);\n    ui_app_update_wt_timeout();\n}\n\nstatic void ui_app_alarm_thread(void* rt_unused(p)) {\n    rt_thread.realtime();\n    rt_thread.name(\"ui_app.alarm\");\n    for (;;) {\n        rt_event_t es[] = { ui_app_wt, ui_app_event_quit };\n        int32_t ix = rt_event.wait_any(rt_countof(es), es);\n        if (ix == 0) {\n            ui_app_post_message(WM_NULL, 0, 0);\n        } else {\n            break;\n        }\n    }\n}\n\n\n// InvalidateRect() may wait for up to 30 milliseconds\n// which is unacceptable for video drawing at monitor\n// refresh rate\n\nstatic void ui_app_redraw_thread(void* rt_unused(p)) {\n    rt_thread.realtime();\n    rt_thread.name(\"ui_app.redraw\");\n    for (;;) {\n        rt_event_t es[] = { ui_app_event_invalidate, ui_app_event_quit };\n        int32_t ix = rt_event.wait_any(rt_countof(es), es);\n        if (ix == 0) {\n            if (ui_app_window() != null) {\n                InvalidateRect(ui_app_window(), null, false);\n            }\n        } else {\n            break;\n        }\n    }\n}\n\n\n// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes\n// https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown\n\nstatic void ui_app_alt_ctrl_shift(bool down, int64_t key) {\n    if (key == VK_MENU)    { ui_app.alt   = down; }\n    if (key == VK_CONTROL) { ui_app.ctrl  = down; }\n    if (key == VK_SHIFT)   { ui_app.shift = down; }\n}\n\nstatic inline ui_point_t ui_app_point2ui(const POINT* p) {\n    ui_point_t u = { p->x, p->y };\n    return u;\n}\n\nstatic inline POINT ui_app_ui2point(const ui_point_t* u) {\n    POINT p = { u->x, u->y };\n    return p;\n}\n\nstatic ui_rect_t ui_app_rect2ui(const RECT* r) {\n    ui_rect_t u = { r->left, r->top, r->right - r->left, r->bottom - r->top };\n    return u;\n}\n\nstatic RECT ui_app_ui2rect(const ui_rect_t* u) {\n    RECT r = { u->x, u->y, u->x + u->w, u->y + u->h };\n    return r;\n}\n\nstatic void ui_app_update_ncm(int32_t dpi) {\n    // Only UTF-16 version supported SystemParametersInfoForDpi\n    rt_fatal_win32err(SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS,\n        sizeof(ui_app_ncm), &ui_app_ncm, 0, (DWORD)dpi));\n}\n\nstatic void ui_app_update_monitor_dpi(HMONITOR monitor, ui_dpi_t* dpi) {\n    dpi->monitor_max = 72;\n    for (int32_t mtd = MDT_EFFECTIVE_DPI; mtd <= MDT_RAW_DPI; mtd++) {\n        uint32_t dpi_x = 0;\n        uint32_t dpi_y = 0;\n        // GetDpiForMonitor() may return ERROR_GEN_FAILURE 0x8007001F when\n        // system wakes up from sleep:\n        // \"\"A device attached to the system is not functioning.\"\n        // docs say:\n        // \"May be used to indicate that the device has stopped responding\n        // or a general failure has occurred on the device.\n        // The device may need to be manually reset.\"\n        int32_t r = GetDpiForMonitor(monitor, (MONITOR_DPI_TYPE)mtd, &dpi_x, &dpi_y);\n        if (r != 0) {\n            rt_thread.sleep_for(1.0 / 32); // and retry:\n            r = GetDpiForMonitor(monitor, (MONITOR_DPI_TYPE)mtd, &dpi_x, &dpi_y);\n        }\n        if (r == 0) {\n            // EFFECTIVE_DPI 168 168 (with regard of user scaling)\n            // ANGULAR_DPI 247 248 (diagonal)\n            // RAW_DPI 283 284 (horizontal, vertical)\n            // Parallels Desktop 16.5.0 (49183) on macOS Mac Book Air\n            // EFFECTIVE_DPI 192 192 (with regard of user scaling)\n            // ANGULAR_DPI 224 224 (diagonal)\n            // RAW_DPI 72 72\n            const int32_t max_xy = (int32_t)rt_max(dpi_x, dpi_y);\n            switch (mtd) {\n                case MDT_EFFECTIVE_DPI:\n                    dpi->monitor_effective = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_effective := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                case MDT_ANGULAR_DPI:\n                    dpi->monitor_angular = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_angular := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                case MDT_RAW_DPI:\n                    dpi->monitor_raw = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_raw := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                default: rt_assert(false);\n            }\n            dpi->monitor_max = rt_max(dpi->monitor_max, max_xy);\n        }\n    }\n//  rt_println(\"ui_app.dpi.monitor_max := %d\", dpi->monitor_max);\n}\n\n#ifndef UI_APP_DEBUG\n\nstatic void ui_app_dump_dpi(void) {\n    rt_println(\"ui_app.dpi.monitor_effective: %d\", ui_app.dpi.monitor_effective  );\n    rt_println(\"ui_app.dpi.monitor_angular  : %d\", ui_app.dpi.monitor_angular    );\n    rt_println(\"ui_app.dpi.monitor_raw      : %d\", ui_app.dpi.monitor_raw        );\n    rt_println(\"ui_app.dpi.monitor_max      : %d\", ui_app.dpi.monitor_max        );\n    rt_println(\"ui_app.dpi.window           : %d\", ui_app.dpi.window             );\n    rt_println(\"ui_app.dpi.system           : %d\", ui_app.dpi.system             );\n    rt_println(\"ui_app.dpi.process          : %d\", ui_app.dpi.process            );\n    rt_println(\"ui_app.mrc      : %d,%d %dx%d\", ui_app.mrc.x, ui_app.mrc.y,\n                                             ui_app.mrc.w, ui_app.mrc.h);\n    rt_println(\"ui_app.wrc      : %d,%d %dx%d\", ui_app.wrc.x, ui_app.wrc.y,\n                                             ui_app.wrc.w, ui_app.wrc.h);\n    rt_println(\"ui_app.crc      : %d,%d %dx%d\", ui_app.crc.x, ui_app.crc.y,\n                                             ui_app.crc.w, ui_app.crc.h);\n    rt_println(\"ui_app.work_area: %d,%d %dx%d\", ui_app.work_area.x, ui_app.work_area.y,\n                                             ui_app.work_area.w, ui_app.work_area.h);\n    int32_t mxt_x = GetSystemMetrics(SM_CXMAXTRACK);\n    int32_t mxt_y = GetSystemMetrics(SM_CYMAXTRACK);\n    rt_println(\"MAXTRACK: %d, %d\", mxt_x, mxt_y);\n    int32_t scr_x = GetSystemMetrics(SM_CXSCREEN);\n    int32_t scr_y = GetSystemMetrics(SM_CYSCREEN);\n    fp64_t monitor_x = (fp64_t)scr_x / (fp64_t)ui_app.dpi.monitor_max;\n    fp64_t monitor_y = (fp64_t)scr_y / (fp64_t)ui_app.dpi.monitor_max;\n    rt_println(\"SCREEN: %d, %d %.1fx%.1f\\\"\", scr_x, scr_y, monitor_x, monitor_y);\n}\n\n#endif\n\nstatic bool ui_app_update_mi(const ui_rect_t* r, uint32_t flags) {\n    RECT rc = ui_app_ui2rect(r);\n    HMONITOR monitor = MonitorFromRect(&rc, flags);\n//  TODO: moving between monitors with different DPIs\n//  HMONITOR mw = MonitorFromWindow(ui_app_window(), flags);\n    if (monitor != null) {\n        ui_app_update_monitor_dpi(monitor, &ui_app.dpi);\n        rt_fatal_win32err(GetMonitorInfoA(monitor, &ui_app_mi));\n        ui_app.work_area = ui_app_rect2ui(&ui_app_mi.rcWork);\n        ui_app.mrc = ui_app_rect2ui(&ui_app_mi.rcMonitor);\n//      ui_app_dump_dpi();\n    }\n    return monitor != null;\n}\n\nstatic void ui_app_update_crc(void) {\n    RECT rc = {0};\n    rt_fatal_win32err(GetClientRect(ui_app_window(), &rc));\n    ui_app.crc = ui_app_rect2ui(&rc);\n}\n\nstatic void ui_app_dispose_fonts(void) {\n    ui_gdi.delete_font(ui_app.fm.prop.normal.font);\n    ui_gdi.delete_font(ui_app.fm.prop.tiny.font);\n    ui_gdi.delete_font(ui_app.fm.prop.title.font);\n    ui_gdi.delete_font(ui_app.fm.prop.rubric.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H1.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H2.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H3.font);\n    memset(&ui_app.fm.prop, 0x00, sizeof(ui_app.fm.prop));\n    ui_gdi.delete_font(ui_app.fm.mono.normal.font);\n    ui_gdi.delete_font(ui_app.fm.mono.tiny.font);\n    ui_gdi.delete_font(ui_app.fm.mono.title.font);\n    ui_gdi.delete_font(ui_app.fm.mono.rubric.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H1.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H2.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H3.font);\n    memset(&ui_app.fm.mono, 0x00, sizeof(ui_app.fm.mono));\n}\n\nstatic fp64_t ui_app_px2pt(fp64_t px) {\n    rt_assert(ui_app.dpi.window >= 72.0);\n    return px * 72.0 / (fp64_t)ui_app.dpi.window;\n}\n\nstatic int32_t ui_app_pt2px(fp64_t pt) { // rounded\n    return (int32_t)(pt * (fp64_t)ui_app.dpi.window / 72.0 + 0.5);\n}\n\nstatic void ui_app_init_cursors(void) {\n    if (ui_app.cursors.arrow == null) {\n        ui_app.cursors.arrow     = (ui_cursor_t)LoadCursorW(null, IDC_ARROW);\n        ui_app.cursors.wait      = (ui_cursor_t)LoadCursorW(null, IDC_WAIT);\n        ui_app.cursors.ibeam     = (ui_cursor_t)LoadCursorW(null, IDC_IBEAM);\n        ui_app.cursors.size_nwse = (ui_cursor_t)LoadCursorW(null, IDC_SIZENWSE);\n        ui_app.cursors.size_nesw = (ui_cursor_t)LoadCursorW(null, IDC_SIZENESW);\n        ui_app.cursors.size_we   = (ui_cursor_t)LoadCursorW(null, IDC_SIZEWE);\n        ui_app.cursors.size_ns   = (ui_cursor_t)LoadCursorW(null, IDC_SIZENS);\n        ui_app.cursors.size_all  = (ui_cursor_t)LoadCursorW(null, IDC_SIZEALL);\n        ui_app.cursor = ui_app.cursors.arrow;\n    }\n}\n\nstatic void ui_app_ncm_dump_fonts(void) {\n    // Win10/Win11 all 5 fonts are exactly the same:\n//  Caption  : Segoe UI 0x-12 weight: 400 quality: 0\n//  SmCaption: Segoe UI 0x-12 weight: 400 quality: 0\n//  Menu     : Segoe UI 0x-12 weight: 400 quality: 0\n//  Status   : Segoe UI 0x-12 weight: 400 quality: 0\n//  Message  : Segoe UI 0x-12 weight: 400 quality: 0\n#if 0\n    const LOGFONTW* fonts[] = {\n        &ui_app_ncm.lfCaptionFont, &ui_app_ncm.lfSmCaptionFont,\n        &ui_app_ncm.lfMenuFont, &ui_app_ncm.lfStatusFont,\n        &ui_app_ncm.lfMessageFont\n    };\n    const char* font_names[] = {\n        \"Caption\", \"SmCaption\", \"Menu\", \"Status\", \"Message\"\n    };\n    for (int32_t i = 0; i < rt_countof(fonts); i++) {\n        const LOGFONTW* lf = fonts[i];\n        char fn[128];\n        rt_str.utf16to8(fn, rt_countof(fn), lf->lfFaceName, -1);\n        rt_println(\"%-9s: %s %dx%d weight: %d quality: %d\", font_names[i], fn,\n                   lf->lfWidth, lf->lfHeight, lf->lfWeight, lf->lfQuality);\n    }\n#endif\n}\n\nstatic void ui_app_dump_font_size(const char* name, const LOGFONTW* lf,\n                                  ui_fm_t* fm) {\n    rt_swear(abs(lf->lfHeight) == fm->height - fm->internal_leading);\n    rt_swear(fm->external_leading == 0); // \"Segoe UI\" and \"Cascadia Mono\"\n    rt_swear(ui_app.dpi.window >= 72);\n    // \"The height, in logical units, of the font's character cell or character.\n    //  The character height value (also known as the em height) is the\n    //  character cell height value minus the internal-leading value.\"\n    #ifdef UI_APP_DUMP_FONT_SIZE\n        int32_t ascender = fm->baseline - fm->ascent;\n        int32_t cell = fm->height - ascender - fm->descent;\n        fp64_t  pt = fm->height * 72.0 / (fp64_t)ui_app.dpi.window;\n        rt_println(\"%-6s .lfH: %+3d h: %d pt: %6.3f \"\n                   \"a: %2d c: %2d d: %d bl: %2d il: %2d lg: %d\",\n                    name, lf->lfHeight, fm->height, pt,\n                    ascender, cell, fm->descent, fm->baseline,\n                    fm->internal_leading, fm->line_gap);\n        #if 0 // TODO: need better understanding of box geometry in\n              // \"design units\"\n            // box scale factor: design units -> pixels\n            fp64_t  sf = pt * 72.0 / (fp64_t)fm->design_units_per_em;\n            sf *= (fp64_t)ui_app.dpi.window / 72.0; // into pixels (unclear???)\n            int32_t bx = (int32_t)(fm->box.x * sf + 0.5);\n            int32_t by = (int32_t)(fm->box.y * sf + 0.5);\n            int32_t bw = (int32_t)(fm->box.w * sf + 0.5);\n            int32_t bh = (int32_t)(fm->box.h * sf + 0.5);\n            rt_println(\"%-6s .box: %d,%d %dx%d\", name, bx, by, bw, bh);\n        #endif\n    #else\n        (void)name; // unused\n    #endif\n}\n\nstatic void ui_app_init_fms(ui_fms_t* fms, const LOGFONTW* base) {\n    LOGFONTW lf = *base;\n    // lf.lfQuality is zero (DEFAULT_QUALITY) that gets internally\n    // interpreted as CLEARTYPE_QUALITY (if clear type is enabled\n    // system wide and it looks really bad on 4K monitors\n    // Experimentally it looks like Windows UI is using PROOF_QUALITY\n    // which is anti-aliased w/o ClearType rainbows\n    // TODO: maybe DEFAULT_QUALITY on 96DPI,\n    //             PROOF_QUALITY below 4K\n    //             ANTIALIASED_QUALITY on 4K and ?\n    lf.lfQuality = ANTIALIASED_QUALITY;\n    ui_gdi.update_fm(&fms->normal, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"normal\", &lf, &fms->normal);\n    const fp64_t fh = lf.lfHeight;\n    rt_swear(fh != 0);\n    lf.lfHeight = (int32_t)(fh * 8.0 / 11.0 + 0.5);\n    ui_gdi.update_fm(&fms->tiny, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"tiny\", &lf, &fms->tiny);\n\n    lf.lfWeight = FW_SEMIBOLD;\n    lf.lfHeight = (int32_t)(fh * 2.25 + 0.5);\n    ui_gdi.update_fm(&fms->title, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"title\", &lf, &fms->title);\n    lf.lfHeight = (int32_t)(fh * 2.00 + 0.5);\n    ui_gdi.update_fm(&fms->rubric, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"rubric\", &lf, &fms->rubric);\n    lf.lfHeight = (int32_t)(fh * 1.75 + 0.5);\n    ui_gdi.update_fm(&fms->H1, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H1\", &lf, &fms->H1);\n    lf.lfHeight = (int32_t)(fh * 1.4 + 0.5);\n    ui_gdi.update_fm(&fms->H2, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H2\", &lf, &fms->H2);\n    lf.lfHeight = (int32_t)(fh * 1.15 + 0.5);\n    ui_gdi.update_fm(&fms->H3, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H3\", &lf, &fms->H3);\n}\n\nstatic void ui_app_init_fonts(int32_t dpi) {\n    ui_app_update_ncm(dpi);\n    ui_app_ncm_dump_fonts();\n    if (ui_app.fm.prop.normal.font != null) { ui_app_dispose_fonts(); }\n    LOGFONTW mono = ui_app_ncm.lfMessageFont;\n    // TODO: how to get name of monospaced from Win32 API?\n    wcscpy_s(mono.lfFaceName, rt_countof(mono.lfFaceName), L\"Cascadia Mono\");\n    mono.lfPitchAndFamily |= FIXED_PITCH;\n//  rt_println(\"ui_app.fm.mono\");\n    ui_app_init_fms(&ui_app.fm.mono, &mono);\n    LOGFONTW prop = ui_app_ncm.lfMessageFont;\n    prop.lfHeight--; // inc by 1\n//  rt_println(\"ui_app.fm.prop\");\n    ui_app_init_fms(&ui_app.fm.prop, &ui_app_ncm.lfMessageFont);\n}\n\nstatic void ui_app_data_save(const char* name, const void* data, int32_t bytes) {\n    rt_config.save(ui_app.class_name, name, data, bytes);\n}\n\nstatic int32_t ui_app_data_size(const char* name) {\n    return rt_config.size(ui_app.class_name, name);\n}\n\nstatic int32_t ui_app_data_load(const char* name, void* data, int32_t bytes) {\n    return rt_config.load(ui_app.class_name, name, data, bytes);\n}\n\ntypedef rt_begin_packed struct ui_app_wiw_s { // \"where is window\"\n    // coordinates in pixels relative (0,0) top left corner\n    // of primary monitor from GetWindowPlacement\n    int32_t    bytes;\n    int32_t    padding;      // to align rectangles and points to 8 bytes\n    ui_rect_t  placement;\n    ui_rect_t  mrc;          // monitor rectangle\n    ui_rect_t  work_area;    // monitor work area (mrc sans taskbar etc)\n    ui_point_t min_position; // not used (-1, -1)\n    ui_point_t max_position; // not used (-1, -1)\n    ui_point_t max_track;    // maximum window size (spawning all monitors)\n    ui_rect_t  space;        // surrounding rect x,y,w,h of all monitors\n    int32_t    dpi;          // of the monitor on which window (x,y) is located\n    int32_t    flags;        // WPF_SETMINPOSITION. WPF_RESTORETOMAXIMIZED\n    int32_t    show;         // show command\n} rt_end_packed ui_app_wiw_t;\n\nstatic BOOL CALLBACK ui_app_monitor_enum_proc(HMONITOR monitor,\n        HDC rt_unused(hdc), RECT* rt_unused(rc1), LPARAM that) {\n    ui_app_wiw_t* wiw = (ui_app_wiw_t*)(uintptr_t)that;\n    MONITORINFOEXA mi = { .cbSize = sizeof(MONITORINFOEXA) };\n    rt_fatal_win32err(GetMonitorInfoA(monitor, (MONITORINFO*)&mi));\n    // monitors can be in negative coordinate spaces and even rotated upside-down\n    const int32_t min_x = rt_min(mi.rcMonitor.left, mi.rcMonitor.right);\n    const int32_t min_y = rt_min(mi.rcMonitor.top,  mi.rcMonitor.bottom);\n    const int32_t max_w = rt_max(mi.rcMonitor.left, mi.rcMonitor.right);\n    const int32_t max_h = rt_max(mi.rcMonitor.top,  mi.rcMonitor.bottom);\n    wiw->space.x = rt_min(wiw->space.x, min_x);\n    wiw->space.y = rt_min(wiw->space.y, min_y);\n    wiw->space.w = rt_max(wiw->space.w, max_w);\n    wiw->space.h = rt_max(wiw->space.h, max_h);\n    return true; // keep going\n}\n\nstatic void ui_app_enum_monitors(ui_app_wiw_t* wiw) {\n    EnumDisplayMonitors(null, null, ui_app_monitor_enum_proc,\n        (LPARAM)(uintptr_t)wiw);\n    // because ui_app_monitor_enum_proc() puts max into w,h:\n    wiw->space.w -= wiw->space.x;\n    wiw->space.h -= wiw->space.y;\n}\n\nstatic void ui_app_save_window_pos(ui_window_t wnd, const char* name, bool dump) {\n    RECT wr = {0};\n    rt_fatal_win32err(GetWindowRect((HWND)wnd, &wr));\n    ui_rect_t wrc = ui_app_rect2ui(&wr);\n    ui_app_update_mi(&wrc, MONITOR_DEFAULTTONEAREST);\n    WINDOWPLACEMENT wpl = { .length = sizeof(wpl) };\n    rt_fatal_win32err(GetWindowPlacement((HWND)wnd, &wpl));\n    // note the replacement of wpl.rcNormalPosition with wrc:\n    ui_app_wiw_t wiw = { // where is window\n        .bytes = sizeof(ui_app_wiw_t),\n        .placement = wrc,\n        .mrc = ui_app.mrc,\n        .work_area = ui_app.work_area,\n        .min_position = ui_app_point2ui(&wpl.ptMinPosition),\n        .max_position = ui_app_point2ui(&wpl.ptMaxPosition),\n        .max_track = {\n            .x = GetSystemMetrics(SM_CXMAXTRACK),\n            .y = GetSystemMetrics(SM_CYMAXTRACK)\n        },\n        .dpi = ui_app.dpi.monitor_max,\n        .flags = (int32_t)wpl.flags,\n        .show  = (int32_t)wpl.showCmd\n    };\n    ui_app_enum_monitors(&wiw);\n    if (dump) {\n        rt_println(\"wiw.space: %d,%d %dx%d\",\n              wiw.space.x, wiw.space.y, wiw.space.w, wiw.space.h);\n        rt_println(\"MAXTRACK: %d, %d\", wiw.max_track.x, wiw.max_track.y);\n        rt_println(\"wpl.rcNormalPosition: %d,%d %dx%d\",\n            wpl.rcNormalPosition.left, wpl.rcNormalPosition.top,\n            wpl.rcNormalPosition.right - wpl.rcNormalPosition.left,\n            wpl.rcNormalPosition.bottom - wpl.rcNormalPosition.top);\n        rt_println(\"wpl.ptMinPosition: %d,%d\",\n            wpl.ptMinPosition.x, wpl.ptMinPosition.y);\n        rt_println(\"wpl.ptMaxPosition: %d,%d\",\n            wpl.ptMaxPosition.x, wpl.ptMaxPosition.y);\n        rt_println(\"wpl.showCmd: %d\", wpl.showCmd);\n        // WPF_SETMINPOSITION. WPF_RESTORETOMAXIMIZED WPF_ASYNCWINDOWPLACEMENT\n        rt_println(\"wpl.flags: %d\", wpl.flags);\n    }\n//  rt_println(\"%d,%d %dx%d show=%d\", wiw.placement.x, wiw.placement.y,\n//      wiw.placement.w, wiw.placement.h, wiw.show);\n    rt_config.save(ui_app.class_name, name, &wiw, sizeof(wiw));\n    ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n}\n\nstatic void ui_app_save_console_pos(void) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null) {\n        ui_app_save_window_pos((ui_window_t)cw, \"wic\", false);\n        HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n        CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n        int32_t r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n        if (r != 0) {\n            rt_println(\"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n        } else {\n            rt_config.save(ui_app.class_name, \"console_screen_buffer_infoex\",\n                            &info, (int32_t)sizeof(info));\n//          rt_println(\"info: %dx%d\", info.dwSize.X, info.dwSize.Y);\n//          rt_println(\"%d,%d %dx%d\", info.srWindow.Left, info.srWindow.Top,\n//              info.srWindow.Right - info.srWindow.Left,\n//              info.srWindow.Bottom - info.srWindow.Top);\n        }\n    }\n    int32_t v = ui_app.is_console_visible();\n    // \"icv\" \"is console visible\"\n    rt_config.save(ui_app.class_name, \"icv\", &v, (int32_t)sizeof(v));\n}\n\nstatic bool ui_app_is_fully_inside(const ui_rect_t* inner,\n                                const ui_rect_t* outer) {\n    return\n        outer->x <= inner->x && inner->x + inner->w <= outer->x + outer->w &&\n        outer->y <= inner->y && inner->y + inner->h <= outer->y + outer->h;\n}\n\nstatic void ui_app_bring_window_inside_monitor(const ui_rect_t* mrc, ui_rect_t* wrc) {\n    rt_assert(mrc->w > 0 && mrc->h > 0);\n    // Check if window rect is inside monitor rect\n    if (!ui_app_is_fully_inside(wrc, mrc)) {\n        // Move window into monitor rect\n        wrc->x = rt_max(mrc->x, rt_min(mrc->x + mrc->w - wrc->w, wrc->x));\n        wrc->y = rt_max(mrc->y, rt_min(mrc->y + mrc->h - wrc->h, wrc->y));\n        // Adjust size to fit into monitor rect\n        wrc->w = rt_min(wrc->w, mrc->w);\n        wrc->h = rt_min(wrc->h, mrc->h);\n    }\n}\n\nstatic bool ui_app_load_window_pos(ui_rect_t* rect, int32_t *visibility) {\n    ui_app_wiw_t wiw = {0}; // where is window\n    bool loaded = rt_config.load(ui_app.class_name, \"wiw\", &wiw, sizeof(wiw)) ==\n                                sizeof(wiw);\n    if (loaded) {\n        #ifdef UI_APP_DEBUG\n            rt_println(\"wiw.placement: %d,%d %dx%d\", wiw.placement.x, wiw.placement.y,\n                wiw.placement.w, wiw.placement.h);\n            rt_println(\"wiw.mrc: %d,%d %dx%d\", wiw.mrc.x, wiw.mrc.y, wiw.mrc.w, wiw.mrc.h);\n            rt_println(\"wiw.work_area: %d,%d %dx%d\", wiw.work_area.x, wiw.work_area.y,\n                                                  wiw.work_area.w, wiw.work_area.h);\n            rt_println(\"wiw.min_position: %d,%d\", wiw.min_position.x, wiw.min_position.y);\n            rt_println(\"wiw.max_position: %d,%d\", wiw.max_position.x, wiw.max_position.y);\n            rt_println(\"wiw.max_track: %d,%d\", wiw.max_track.x, wiw.max_track.y);\n            rt_println(\"wiw.dpi: %d\", wiw.dpi);\n            rt_println(\"wiw.flags: %d\", wiw.flags);\n            rt_println(\"wiw.show: %d\", wiw.show);\n        #endif\n        ui_app_update_mi(&wiw.placement, MONITOR_DEFAULTTONEAREST);\n        bool same_monitor = memcmp(&wiw.mrc, &ui_app.mrc, sizeof(wiw.mrc)) == 0;\n//      rt_println(\"%d,%d %dx%d\", p->x, p->y, p->w, p->h);\n        if (same_monitor) {\n            *rect = wiw.placement;\n        } else { // moving to another monitor\n            rect->x = (wiw.placement.x - wiw.mrc.x) * ui_app.mrc.w / wiw.mrc.w;\n            rect->y = (wiw.placement.y - wiw.mrc.y) * ui_app.mrc.h / wiw.mrc.h;\n            // adjust according to monitors DPI difference:\n            // (w, h) theoretically could be as large as 0xFFFF\n            const int64_t w = (int64_t)wiw.placement.w * ui_app.dpi.monitor_max;\n            const int64_t h = (int64_t)wiw.placement.h * ui_app.dpi.monitor_max;\n            rect->w = (int32_t)(w / wiw.dpi);\n            rect->h = (int32_t)(h / wiw.dpi);\n        }\n        *visibility = wiw.show;\n    }\n//  rt_println(\"%d,%d %dx%d show=%d\", rect->x, rect->y, rect->w, rect->h, *visibility);\n    ui_app_bring_window_inside_monitor(&ui_app.mrc, rect);\n//  rt_println(\"%d,%d %dx%d show=%d\", rect->x, rect->y, rect->w, rect->h, *visibility);\n    return loaded;\n}\n\nstatic bool ui_app_load_console_pos(ui_rect_t* rect, int32_t *visibility) {\n    ui_app_wiw_t wiw = {0}; // where is window\n    *visibility = 0; // boolean\n    bool loaded = rt_config.load(ui_app.class_name, \"wic\", &wiw, sizeof(wiw)) ==\n                                sizeof(wiw);\n    if (loaded) {\n        ui_app_update_mi(&wiw.placement, MONITOR_DEFAULTTONEAREST);\n        bool same_monitor = memcmp(&wiw.mrc, &ui_app.mrc, sizeof(wiw.mrc)) == 0;\n//      rt_println(\"%d,%d %dx%d\", p->x, p->y, p->w, p->h);\n        if (same_monitor) {\n            *rect = wiw.placement;\n        } else { // moving to another monitor\n            rect->x = (wiw.placement.x - wiw.mrc.x) * ui_app.mrc.w / wiw.mrc.w;\n            rect->y = (wiw.placement.y - wiw.mrc.y) * ui_app.mrc.h / wiw.mrc.h;\n            // adjust according to monitors DPI difference:\n            // (w, h) theoretically could be as large as 0xFFFF\n            const int64_t w = (int64_t)wiw.placement.w * ui_app.dpi.monitor_max;\n            const int64_t h = (int64_t)wiw.placement.h * ui_app.dpi.monitor_max;\n            rect->w = (int32_t)(w / wiw.dpi);\n            rect->h = (int32_t)(h / wiw.dpi);\n        }\n        *visibility = wiw.show != 0;\n        ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n    }\n    return loaded;\n}\n\nstatic void ui_app_timer_kill(ui_timer_t timer) {\n    rt_fatal_win32err(KillTimer(ui_app_window(), timer));\n}\n\nstatic ui_timer_t ui_app_timer_set(uintptr_t id, int32_t ms) {\n    rt_not_null(ui_app_window());\n    rt_assert(10 <= ms && ms < 0x7FFFFFFF);\n    ui_timer_t tid = (ui_timer_t)SetTimer(ui_app_window(), id, (uint32_t)ms, null);\n    rt_fatal_if(tid == 0);\n    rt_assert(tid == id);\n    return tid;\n}\n\nstatic void ui_app_timer(ui_view_t* view, ui_timer_t id) {\n    ui_view.timer(view, id);\n    if (id == ui_app_timer_1s_id) { ui_view.every_sec(view); }\n    if (id == ui_app_timer_100ms_id) { ui_view.every_100ms(view); }\n}\n\nstatic void ui_app_animate_timer(void) {\n    ui_app_post_message(ui.message.animate, (int64_t)ui_app_animate.step + 1,\n        (int64_t)(uintptr_t)ui_app_animate.f);\n}\n\nstatic void ui_app_wm_timer(ui_timer_t id) {\n    if (ui_app.animating.time != 0 && ui_app.now > ui_app.animating.time) {\n        ui_app.show_toast(null, 0);\n    }\n    if (ui_app_animate.timer == id) { ui_app_animate_timer(); }\n    ui_app_timer(ui_app.root, id);\n}\n\nstatic void ui_app_window_dpi(void) {\n    int32_t dpi = (int32_t)GetDpiForWindow(ui_app_window());\n    if (dpi == 0) { dpi = (int32_t)GetDpiForWindow(GetParent(ui_app_window())); }\n    if (dpi == 0) { dpi = (int32_t)GetDpiForWindow(GetDesktopWindow()); }\n    if (dpi == 0) { dpi = (int32_t)GetSystemDpiForProcess(GetCurrentProcess()); }\n    if (dpi == 0) { dpi = (int32_t)GetDpiForSystem(); }\n    ui_app.dpi.window = dpi;\n}\n\nstatic void ui_app_window_opening(void) {\n    ui_app_window_dpi();\n    ui_app_init_fonts(ui_app.dpi.window);\n    ui_app_init_cursors();\n    ui_app_timer_1s_id = ui_app.set_timer((uintptr_t)&ui_app_timer_1s_id, 1000);\n    ui_app_timer_100ms_id = ui_app.set_timer((uintptr_t)&ui_app_timer_100ms_id, 100);\n    rt_assert(ui_app.cursors.arrow != null);\n    ui_app.set_cursor(ui_app.cursors.arrow);\n    ui_app.canvas = (ui_canvas_t)GetDC(ui_app_window());\n    rt_not_null(ui_app.canvas);\n    if (ui_app.opened != null) { ui_app.opened(); }\n    ui_view.set_text(ui_app.root, \"ui_app.root\"); // debugging\n    ui_app_wm_timer(ui_app_timer_100ms_id);\n    ui_app_wm_timer(ui_app_timer_1s_id);\n    rt_fatal_if(ReleaseDC(ui_app_window(), ui_app_canvas()) == 0);\n    ui_app.canvas = null;\n    ui_app.request_layout(); // request layout\n    if (ui_app.last_visibility == ui.visibility.maximize) {\n        ShowWindow(ui_app_window(), ui.visibility.maximize);\n    }\n//  ui_app_dump_dpi();\n//  if (forced_locale != 0) {\n//      SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (uintptr_t)\"intl\", 0, 1000, null);\n//  }\n}\n\nstatic void ui_app_window_closing(void) {\n    if (ui_app.can_close == null || ui_app.can_close()) {\n        if (ui_app.is_full_screen) { ui_app.full_screen(false); }\n        ui_app.kill_timer(ui_app_timer_1s_id);\n        ui_app.kill_timer(ui_app_timer_100ms_id);\n        ui_app_timer_1s_id = 0;\n        ui_app_timer_100ms_id = 0;\n        if (ui_app.closed != null) { ui_app.closed(); }\n        ui_app_save_window_pos(ui_app.window, \"wiw\", false);\n        ui_app_save_console_pos();\n        DestroyWindow(ui_app_window());\n        ui_app.window = null;\n    }\n}\n\nstatic void ui_app_get_min_max_info(MINMAXINFO* mmi) {\n    const ui_window_sizing_t* ws = &ui_app.window_sizing;\n    const ui_rect_t* wa = &ui_app.work_area;\n    const int32_t min_w = ws->min_w > 0 ? ui_app.in2px(ws->min_w) : ui_app.in2px(1.0);\n    const int32_t min_h = ws->min_h > 0 ? ui_app.in2px(ws->min_h) : ui_app.in2px(0.5);\n    mmi->ptMinTrackSize.x = min_w;\n    mmi->ptMinTrackSize.y = min_h;\n    const int32_t max_w = ws->max_w > 0 ? ui_app.in2px(ws->max_w) : wa->w;\n    const int32_t max_h = ws->max_h > 0 ? ui_app.in2px(ws->max_h) : wa->h;\n    if (ui_app.no_clip) {\n        mmi->ptMaxTrackSize.x = max_w;\n        mmi->ptMaxTrackSize.y = max_h;\n    } else {\n        // clip max_w and max_h to monitor work area\n        mmi->ptMaxTrackSize.x = rt_min(max_w, wa->w);\n        mmi->ptMaxTrackSize.y = rt_min(max_h, wa->h);\n    }\n    mmi->ptMaxSize.x = mmi->ptMaxTrackSize.x;\n    mmi->ptMaxSize.y = mmi->ptMaxTrackSize.y;\n}\n\nstatic void ui_app_paint(ui_view_t* view) {\n    rt_assert(ui_app_window() != null);\n    // crc = {0,0} on minimized windows but paint is still called\n    if (ui_app.crc.w > 0 && ui_app.crc.h > 0) { ui_view.paint(view); }\n}\n\nstatic void ui_app_measure_and_layout(ui_view_t* view) {\n    // restore from minimized calls ui_app.crc.w,h == 0\n    if (ui_app.crc.w > 0 && ui_app.crc.h > 0 && ui_app_window() != null) {\n        ui_view.measure(view);\n        ui_view.layout(view);\n        ui_app_layout_dirty = false;\n    }\n}\n\nstatic void ui_app_toast_character(const char* utf8);\nstatic bool ui_app_toast_key_pressed(int64_t key);\nstatic bool ui_app_toast_tap(ui_view_t* v, int32_t ix, bool pressed);\n\nstatic void ui_app_dispatch_wm_char(ui_view_t* view, const uint16_t* utf16) {\n    char utf8[32 + 1];\n    int32_t utf8bytes = rt_str.utf8_bytes(utf16, -1);\n    rt_swear(utf8bytes < rt_countof(utf8) - 1); // 32 bytes + 0x00\n    rt_str.utf16to8(utf8, rt_countof(utf8), utf16, -1);\n    utf8[utf8bytes] = 0x00;\n    if (ui_app.animating.view != null) {\n        ui_app_toast_character(utf8);\n    } else {\n        ui_view.character(view, utf8);\n    }\n    ui_app_high_surrogate = 0x0000;\n}\n\nstatic void ui_app_wm_char(ui_view_t* view, const uint16_t* utf16) {\n    int32_t utf16chars = rt_str.len16(utf16);\n    rt_swear(0 < utf16chars && utf16chars < 4); // wParam is 64bits\n    const uint16_t utf16char = utf16[0];\n    if (utf16chars == 1 && rt_str.utf16_is_high_surrogate(utf16char)) {\n        ui_app_high_surrogate = utf16char;\n    } else if (utf16chars == 1 && rt_str.utf16_is_low_surrogate(utf16char)) {\n        if (ui_app_high_surrogate != 0) {\n            uint16_t utf16_surrogate_pair[3] = {\n                ui_app_high_surrogate,\n                utf16char,\n                0x0000\n            };\n            ui_app_dispatch_wm_char(view, utf16_surrogate_pair);\n        }\n    } else {\n        ui_app_dispatch_wm_char(view, utf16);\n    }\n}\n\nstatic bool ui_app_wm_key_pressed(ui_view_t* v, int64_t key) {\n    if (ui_app.animating.view != null) {\n        return ui_app_toast_key_pressed(key);\n    } else {\n        return ui_view.key_pressed(v, key);\n    }\n}\n\nstatic bool ui_app_mouse(ui_view_t* v, int32_t m, int64_t f) {\n    bool swallow = false;\n    // override ui_app_update_mouse_buttons_state() (sic):\n    // because mouse message can be from the past\n    ui_app.mouse_left   = f & (ui_app.mouse_swapped ? MK_RBUTTON : MK_LBUTTON);\n    ui_app.mouse_middle = f & MK_MBUTTON;\n    ui_app.mouse_right  = f & (ui_app.mouse_swapped ? MK_LBUTTON : MK_RBUTTON);\n    ui_view_t* av = ui_app.animating.view;\n    if (m == WM_MOUSEHOVER) {\n        ui_view.mouse_hover(av != null && av->mouse_hover != null ? av : v);\n    } else if (m == WM_MOUSEMOVE) {\n        ui_view.mouse_move(av != null && av->mouse_move != null ? av : v);\n    } else if (m == WM_LBUTTONDOWN  ||\n               m == WM_LBUTTONUP    ||\n               m == WM_MBUTTONDOWN  ||\n               m == WM_MBUTTONUP    ||\n               m == WM_RBUTTONDOWN  ||\n               m == WM_RBUTTONUP) {\n        const int i =\n             (m == WM_LBUTTONDOWN || m == WM_LBUTTONUP) ? 0 :\n            ((m == WM_MBUTTONDOWN || m == WM_MBUTTONUP) ? 1 :\n            ((m == WM_RBUTTONDOWN || m == WM_RBUTTONUP) ? 2 : -1));\n        rt_swear(i >= 0);\n        const int32_t ix = ui_app.mouse_swapped ? 2 - i : i;\n        const bool pressed =\n            m == WM_LBUTTONDOWN ||\n            m == WM_MBUTTONDOWN ||\n            m == WM_RBUTTONDOWN;\n        if (av != null) {\n            // because of \"micro\" close button:\n            swallow = ui_app_toast_tap(ui_app.animating.view, ix, pressed);\n        } else {\n            if (av != null && av->tap != null) {\n                swallow = ui_view.tap(av, ix, pressed);\n            } else {\n                // tap detector will handle the tap() calling\n            }\n        }\n    } else if (m == WM_LBUTTONDBLCLK ||\n               m == WM_MBUTTONDBLCLK ||\n               m == WM_RBUTTONDBLCLK) {\n        const int i =\n             (m == WM_LBUTTONDBLCLK) ? 0 :\n            ((m == WM_MBUTTONDBLCLK) ? 1 :\n            ((m == WM_RBUTTONDBLCLK) ? 2 : -1));\n        rt_swear(i >= 0);\n        if (av != null && av->double_tap != null) {\n            const int32_t ix = ui_app.mouse_swapped ? 2 - i : i;\n            swallow = ui_view.double_tap(av, ix);\n        }\n        // otherwise tap detector will do the double_tap() call\n    } else {\n        rt_assert(false, \"m: 0x%04X\", m);\n    }\n    return swallow;\n}\n\nstatic void ui_app_show_sys_menu(int32_t x, int32_t y) {\n    HMENU sys_menu = GetSystemMenu(ui_app_window(), false);\n    if (sys_menu != null) {\n        // TPM_RIGHTBUTTON means both left and right click to select menu item\n        const DWORD flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON |\n                            TPM_RETURNCMD | TPM_VERPOSANIMATION;\n        int32_t sys_cmd = TrackPopupMenu(sys_menu, flags, x, y, 0,\n                                         ui_app_window(), null);\n        if (sys_cmd != 0) {\n            ui_app_post_message(WM_SYSCOMMAND, sys_cmd, 0);\n        }\n    }\n}\n\nstatic int32_t ui_app_nc_mouse_message(int32_t m) {\n    switch (m) {\n        case WM_NCMOUSEMOVE     : return WM_MOUSEMOVE;\n        case WM_NCLBUTTONDOWN   : return WM_LBUTTONDOWN;\n        case WM_NCLBUTTONUP     : return WM_LBUTTONUP;\n        case WM_NCLBUTTONDBLCLK : return WM_LBUTTONDBLCLK;\n        case WM_NCMBUTTONDOWN   : return WM_MBUTTONDOWN;\n        case WM_NCMBUTTONUP     : return WM_MBUTTONUP;\n        case WM_NCMBUTTONDBLCLK : return WM_MBUTTONDBLCLK;\n        case WM_NCRBUTTONDOWN   : return WM_RBUTTONDOWN;\n        case WM_NCRBUTTONUP     : return WM_RBUTTONUP;\n        case WM_NCRBUTTONDBLCLK : return WM_RBUTTONDBLCLK;\n        default: rt_swear(false, \"fix me m: %d\", m);\n    }\n    return -1;\n}\n\nstatic bool ui_app_nc_mouse_buttons(int32_t m, int64_t wp, int64_t lp) {\n    bool swallow = false;\n    POINT screen = {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)};\n    POINT client = screen;\n    ScreenToClient(ui_app_window(), &client);\n    ui_app.mouse = ui_app_point2ui(&client);\n    const bool inside = ui_view.inside(ui_app.caption, &ui_app.mouse);\n    if (!ui_view.is_hidden(ui_app.caption) && inside) {\n        uint16_t lr = ui_app.mouse_swapped ? WM_NCLBUTTONDOWN : WM_NCRBUTTONDOWN;\n        if (m == lr) {\n//          rt_println(\"WM_NC*BUTTONDOWN %d %d\", ui_app.mouse.x, ui_app.mouse.y);\n            swallow = true;\n            ui_app_show_sys_menu(screen.x, screen.y);\n        }\n    } else {\n        swallow = ui_app_mouse(ui_app.root, ui_app_nc_mouse_message(m), wp);\n    }\n    return swallow;\n}\n\nenum { ui_app_animation_steps = 63 };\n\nstatic void ui_app_toast_paint(void) {\n    static ui_bitmap_t image_dark;\n    if (image_dark.texture == null) {\n        uint8_t pixels[4] = { 0x3F, 0x3F, 0x3F };\n        ui_gdi.bitmap_init(&image_dark, 1, 1, 3, pixels);\n    }\n    static ui_bitmap_t image_light;\n    if (image_dark.texture == null) {\n        uint8_t pixels[4] = { 0xC0, 0xC0, 0xC0 };\n        ui_gdi.bitmap_init(&image_light, 1, 1, 3, pixels);\n    }\n    ui_view_t* av = ui_app.animating.view;\n    if (av != null) {\n        ui_view.measure(av);\n        bool hint = ui_app.animating.x >= 0 && ui_app.animating.y >= 0;\n        const int32_t em_w = av->fm->em.w;\n        const int32_t em_h = av->fm->em.h;\n        if (!hint) {\n            rt_assert(0 <= ui_app.animating.step && ui_app.animating.step < ui_app_animation_steps);\n            int32_t step = ui_app.animating.step - (ui_app_animation_steps - 1);\n            av->y = av->h * step / (ui_app_animation_steps - 1);\n//          rt_println(\"step=%d of %d y=%d\", ui_app.animating.step,\n//                  ui_app_toast_steps, av->y);\n            ui_app_measure_and_layout(av);\n            // dim main window (as `disabled`):\n            fp64_t alpha = rt_min(0.40, 0.40 * ui_app.animating.step / (fp64_t)ui_app_animation_steps);\n            ui_gdi.alpha(0, 0, ui_app.crc.w, ui_app.crc.h,\n                         0, 0, image_dark.w, image_dark.h,\n                        &image_dark, alpha);\n            av->x = (ui_app.root->w - av->w) / 2;\n//          rt_println(\"ui_app.animating.y: %d av->y: %d\",\n//                  ui_app.animating.y, av->y);\n        } else {\n            av->x = ui_app.animating.x;\n            av->y = ui_app.animating.y;\n            ui_app_measure_and_layout(av);\n            int32_t mx = ui_app.root->w - av->w - em_w;\n            int32_t cx = ui_app.animating.x - av->w / 2;\n            av->x = rt_min(mx, rt_max(0, cx));\n            av->y = rt_min(\n                ui_app.root->h - em_h, rt_max(0, ui_app.animating.y));\n//          rt_println(\"ui_app.animating.y: %d av->y: %d\",\n//                  ui_app.animating.y, av->y);\n        }\n        int32_t x = av->x - em_w / 4;\n        int32_t y = av->y - em_h / 8;\n        int32_t w = av->w + em_w / 2;\n        int32_t h = av->h + em_h / 4;\n        int32_t radius = em_w / 2;\n        if (radius % 2 == 0) { radius++; }\n        ui_color_t color = ui_theme.is_app_dark() ?\n            ui_color_rgb(45, 45, 48) : // TODO: hard coded\n            ui_colors.get_color(ui_color_id_button_face);\n        ui_color_t tint = ui_colors.interpolate(color, ui_colors.yellow, 0.5f);\n        ui_gdi.rounded(x, y, w, h, radius, tint, tint);\n        if (!hint) { av->y += em_h / 4; }\n        ui_app_paint(av);\n        if (!hint) {\n            if (av->y == em_h / 4) {\n                // micro \"close\" toast button:\n                int32_t r = av->x + av->w;\n                const int32_t tx = r - em_w / 2;\n                const int32_t ty = 0;\n                const ui_gdi_ta_t ta = {\n                    .fm = &ui_app.fm.prop.normal,\n                    .color = ui_color_undefined,\n                    .color_id = ui_color_id_window_text\n                };\n                ui_gdi.text(&ta, tx, ty, \"%s\",\n                                 rt_glyph_multiplication_sign);\n            }\n        }\n    }\n}\n\nstatic void ui_app_toast_cancel(void) {\n    if (ui_app.animating.view != null) {\n        if (ui_app.animating.view->type == ui_view_mbx) {\n            ui_mbx_t* mx = (ui_mbx_t*)ui_app.animating.view;\n            if (mx->option < 0 && mx->callback != null) {\n                mx->callback(&mx->view);\n            }\n        }\n        ui_app.animating.view->parent = null;\n        ui_app.animating.step = 0;\n        ui_app.animating.view = null;\n        ui_app.animating.time = 0;\n        ui_app.animating.x = -1;\n        ui_app.animating.y = -1;\n        if (ui_app.animating.focused != null) {\n            ui_view.set_focus(ui_app.animating.focused->focusable &&\n               !ui_view.is_hidden(ui_app.animating.focused) &&\n               !ui_view.is_disabled(ui_app.animating.focused) ?\n                ui_app.animating.focused : null);\n            ui_app.animating.focused = null;\n        } else {\n            ui_view.set_focus(null);\n        }\n        ui_app.request_redraw();\n    }\n}\n\nstatic bool ui_app_toast_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false;\n    rt_swear(v == ui_app.animating.view);\n    if (pressed) {\n        const ui_fm_t* fm = v->fm;\n        const int32_t right = v->x + v->w;\n        const int32_t x = right - fm->em.w / 2;\n        const int32_t mx = ui_app.mouse.x;\n        const int32_t my = ui_app.mouse.y;\n        // micro close button which is not a button\n        if (x <= mx && mx <= x + fm->em.w && 0 <= my && my <= fm->em.h) {\n            ui_app_toast_cancel();\n        }\n    }\n    if (ui_app.animating.view != null) { // could have been canceled above\n        swallow = ui_view.tap(v, ix, pressed); // TODO: do we need it?\n    }\n    return swallow;\n}\n\nstatic void ui_app_toast_character(const char* utf8) {\n    char ch = utf8[0];\n    if (ui_app.animating.view != null && ch == 033) { // ESC traditionally in octal\n        ui_app_toast_cancel();\n        ui_app.show_toast(null, 0);\n    } else {\n        ui_view.character(ui_app.animating.view, utf8);\n    }\n}\n\nstatic bool ui_app_toast_key_pressed(int64_t key) {\n    if (ui_app.animating.view != null && key == 033) { // ESC traditionally in octal\n        ui_app_toast_cancel();\n        ui_app.show_toast(null, 0);\n        return true;\n    } else {\n        return ui_view.key_pressed(ui_app.animating.view, key);\n    }\n}\n\nstatic void ui_app_toast_dim(int32_t step) {\n    ui_app.animating.step = step;\n    ui_app.request_redraw();\n    UpdateWindow(ui_app_window());\n}\n\nstatic void ui_app_animate_step(ui_app_animate_function_t f, int32_t step, int32_t steps) {\n    // calls function(0..step-1) exactly step times\n    bool cancel = false;\n    if (f != null && f != ui_app_animate.f && step == 0 && steps > 0) {\n        // start animated_groot\n        ui_app_animate.count = steps;\n        ui_app_animate.f = f;\n        f(step);\n        ui_app_animate.timer = ui_app.set_timer((uintptr_t)&ui_app_animate.timer, 10);\n    } else if (f != null && ui_app_animate.f == f && step > 0) {\n        cancel = step >= ui_app_animate.count;\n        if (!cancel) {\n            ui_app_animate.step = step;\n            f(step);\n        }\n    } else if (f == null) {\n        cancel = true;\n    }\n    if (cancel) {\n        if (ui_app_animate.timer != 0) {\n            ui_app.kill_timer(ui_app_animate.timer);\n        }\n        ui_app_animate.step = 0;\n        ui_app_animate.timer = 0;\n        ui_app_animate.f = null;\n        ui_app_animate.count = 0;\n    }\n}\n\nstatic void ui_app_animate_start(ui_app_animate_function_t f, int32_t steps) {\n    // calls f(0..step-1) exactly steps times, unless cancelled with call\n    // animate(null, 0) or animate(other_function, n > 0)\n    ui_app_animate_step(f, 0, steps);\n}\n\nstatic void ui_app_view_paint(ui_view_t* v) {\n    v->color = ui_colors.get_color(v->color_id);\n    if (v->background_id > 0) {\n        v->background = ui_colors.get_color(v->background_id);\n    }\n    if (!ui_color_is_undefined(v->background) &&\n        !ui_color_is_transparent(v->background)) {\n        ui_gdi.fill(v->x, v->y, v->w, v->h, v->background);\n    }\n}\n\nstatic void ui_app_view_layout(void) {\n    rt_not_null(ui_app.window);\n    rt_not_null(ui_app.canvas);\n    if (ui_app.no_decor) {\n        ui_app.root->x = ui_app.border.w;\n        ui_app.root->y = ui_app.border.h;\n        ui_app.root->w = ui_app.crc.w - ui_app.border.w * 2;\n        ui_app.root->h = ui_app.crc.h - ui_app.border.h * 2;\n    } else {\n        ui_app.root->x = 0;\n        ui_app.root->y = 0;\n        ui_app.root->w = ui_app.crc.w;\n        ui_app.root->h = ui_app.crc.h;\n    }\n    ui_app_measure_and_layout(ui_app.root);\n}\n\nstatic void ui_app_view_active_frame_paint(void) {\n    ui_color_t c = ui_app.is_active() ?\n        ui_colors.get_color(ui_color_id_highlight) : // ui_colors.btn_hover_highlight\n        ui_colors.get_color(ui_color_id_inactive_title);\n    rt_assert(ui_app.border.w == ui_app.border.h);\n    const int32_t w = ui_app.wrc.w;\n    const int32_t h = ui_app.wrc.h;\n    for (int32_t i = 0; i < ui_app.border.w; i++) {\n        ui_gdi.frame(i, i, w - i * 2, h - i * 2, c);\n    }\n}\n\nstatic void ui_app_paint_stats(void) {\n    if (ui_app.paint_count % 128 == 0) { ui_app.paint_max = 0; }\n    ui_app.paint_time = rt_clock.seconds() - ui_app.now;\n    ui_app.paint_max = rt_max(ui_app.paint_time, ui_app.paint_max);\n    if (ui_app.paint_avg == 0) {\n        ui_app.paint_avg = ui_app.paint_time;\n    } else { // EMA over 32 paint() calls\n        ui_app.paint_avg = ui_app.paint_avg * (1.0 - 1.0 / 32.0) +\n                        ui_app.paint_time / 32.0;\n    }\n    static fp64_t first_paint;\n    if (first_paint == 0) { first_paint = ui_app.now; }\n    fp64_t since_first_paint = ui_app.now - first_paint;\n    if (since_first_paint > 0) {\n        double fps = (double)ui_app.paint_count / since_first_paint;\n        if (ui_app.paint_fps == 0) {\n            ui_app.paint_fps = fps;\n        } else {\n            ui_app.paint_fps = ui_app.paint_fps * (1.0 - 1.0 / 32.0) + fps / 32.0;\n        }\n    }\n    if (ui_app.paint_last == 0) {\n        ui_app.paint_dt_min = 1.0 / 60.0; // 60Hz monitor\n    } else {\n        fp64_t since_last = ui_app.now - ui_app.paint_last;\n        if (since_last > 1.0 / 120.0) { // 240Hz monitor\n            ui_app.paint_dt_min = rt_min(ui_app.paint_dt_min, since_last);\n        }\n//      rt_println(\"paint_dt_min: %.6f since_last: %.6f\",\n//              ui_app.paint_dt_min, since_last);\n    }\n    ui_app.paint_last = ui_app.now;\n}\n\nstatic void ui_app_paint_on_canvas(HDC hdc) {\n    ui_canvas_t canvas = ui_app.canvas;\n    ui_app.canvas = (ui_canvas_t)hdc;\n    ui_app_update_crc();\n    if (ui_app_layout_dirty) {\n        ui_app_view_layout();\n    }\n    ui_gdi.begin(null);\n    ui_app_paint(ui_app.root);\n    if (ui_app.animating.view != null) { ui_app_toast_paint(); }\n    // active frame on top of everything:\n    if (ui_app.no_decor && !ui_app.is_full_screen &&\n        !ui_app.is_maximized()) {\n        ui_app_view_active_frame_paint();\n    }\n    ui_gdi.end();\n    ui_app.paint_count++;\n    ui_app.canvas = canvas;\n    ui_app_paint_stats();\n}\n\nstatic void ui_app_wm_paint(void) {\n    // it is possible to receive WM_PAINT when window is not closed\n    if (ui_app.window != null) {\n        PAINTSTRUCT ps = {0};\n        BeginPaint(ui_app_window(), &ps);\n        ui_app.prc = ui_app_rect2ui(&ps.rcPaint);\n//      rt_println(\"%d,%d %dx%d\", ui_app.prc.x, ui_app.prc.y, ui_app.prc.w, ui_app.prc.h);\n        ui_app_paint_on_canvas(ps.hdc);\n        EndPaint(ui_app_window(), &ps);\n    }\n}\n\n// about (x,y) being (-32000,-32000) see:\n// https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/ui/views/win/hwnd_message_handler.cc#1847\n\nstatic void ui_app_window_position_changed(const WINDOWPOS* wp) {\n    ui_app.root->state.hidden = !IsWindowVisible(ui_app_window());\n    const bool moved  = (wp->flags & SWP_NOMOVE) == 0;\n    const bool sized  = (wp->flags & SWP_NOSIZE) == 0;\n    const bool hiding = (wp->flags & SWP_HIDEWINDOW) != 0 ||\n                        (wp->x == -32000 && wp->y == -32000);\n    HMONITOR monitor = MonitorFromWindow(ui_app_window(), MONITOR_DEFAULTTONULL);\n    if (!ui_app.root->state.hidden && (moved || sized) &&\n        !hiding && monitor != null) {\n        RECT wrc = ui_app_ui2rect(&ui_app.wrc);\n        rt_fatal_win32err(GetWindowRect(ui_app_window(), &wrc));\n        ui_app.wrc = ui_app_rect2ui(&wrc);\n        ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n        ui_app_update_crc();\n        if (ui_app_timer_1s_id != 0) { ui_app.request_layout(); }\n    }\n}\n\nstatic void ui_app_setting_change(uintptr_t wp, uintptr_t lp) {\n    // wp: SPI_SETWORKAREA ... SPI_SETDOCKMOVING\n    //     SPI_GETACTIVEWINDOWTRACKING ... SPI_SETGESTUREVISUALIZATION\n    if (wp == SPI_SETLOGICALDPIOVERRIDE) {\n        ui_app_init_fonts(ui_app.dpi.window); // font scale changed\n        ui_app.request_layout();\n    } else if (lp != 0 &&\n       (strcmp((const char*)lp, \"ImmersiveColorSet\") == 0 ||\n        wcscmp((const uint16_t*)lp, L\"ImmersiveColorSet\") == 0)) {\n        // expected:\n        // SPI_SETICONTITLELOGFONT 0x22 ?\n        // SPI_SETNONCLIENTMETRICS 0x2A ?\n//      rt_println(\"wp: 0x%08X\", wp);\n        // actual wp == 0x0000\n        ui_theme.refresh();\n    } else if (wp == 0 && lp != 0 && strcmp((const char*)lp, \"intl\") == 0) {\n        rt_println(\"wp: 0x%04X\", wp); // SPI_SETLOCALEINFO 0x24 ?\n        uint16_t ln[LOCALE_NAME_MAX_LENGTH + 1];\n        int32_t n = GetUserDefaultLocaleName(ln, rt_countof(ln));\n        rt_fatal_if(n <= 0);\n        uint16_t rln[LOCALE_NAME_MAX_LENGTH + 1];\n        n = ResolveLocaleName(ln, rln, rt_countof(rln));\n        rt_fatal_if(n <= 0);\n        LCID lc_id = LocaleNameToLCID(rln, LOCALE_ALLOW_NEUTRAL_NAMES);\n        rt_fatal_win32err(SetThreadLocale(lc_id));\n    }\n}\n\nstatic void ui_app_show_task_bar(bool show) {\n    HWND taskbar = FindWindowA(\"Shell_TrayWnd\", null);\n    if (taskbar != null) {\n        ShowWindow(taskbar, show ? SW_SHOW : SW_HIDE);\n        UpdateWindow(taskbar);\n    }\n}\n\nstatic bool ui_app_click_detector(uint32_t msg, WPARAM wp, LPARAM lp) {\n    bool swallow = false;\n    enum { tap = 1, long_press = 2, double_tap = 3 };\n    // TODO: click detector does not handle WM_NCLBUTTONDOWN, ...\n    //       it can be modified to do so if needed\n    #pragma push_macro(\"ui_set_timer\")\n    #pragma push_macro(\"ui_kill_timer\")\n    #pragma push_macro(\"ui_timers_done\")\n\n    #define ui_set_timer(t, ms) do {                 \\\n        rt_assert(t == 0);                           \\\n        t = ui_app_timer_set((uintptr_t)&t, ms);     \\\n    } while (0)\n\n    #define ui_kill_timer(t) do {                    \\\n        if (t != 0) { ui_app_timer_kill(t); t = 0; } \\\n    } while (0)\n\n    #define ui_timers_done(ix) do {                  \\\n        clicked[ix] = 0;                             \\\n        pressed[ix] = false;                         \\\n        click_at[ix] = (ui_point_t){0, 0};           \\\n        ui_kill_timer(timer_p[ix]);                  \\\n        ui_kill_timer(timer_d[ix]);                  \\\n    } while (0)\n\n    // This function should work regardless to CS_BLKCLK being present\n    // 0: Left, 1: Middle, 2: Right\n    static ui_point_t click_at[3];\n    static fp64_t     clicked[3]; // click time\n    static bool       pressed[3];\n    static ui_timer_t timer_d[3]; // double tap\n    static ui_timer_t timer_p[3]; // long press\n    bool up = false;\n    int32_t ix = -1;\n    int32_t m = 0;\n    switch (msg) {\n        case WM_LBUTTONDOWN  : ix = 0; m = tap;        break;\n        case WM_MBUTTONDOWN  : ix = 1; m = tap;        break;\n        case WM_RBUTTONDOWN  : ix = 2; m = tap;        break;\n        case WM_LBUTTONDBLCLK: ix = 0; m = double_tap; break;\n        case WM_MBUTTONDBLCLK: ix = 1; m = double_tap; break;\n        case WM_RBUTTONDBLCLK: ix = 2; m = double_tap; break;\n        case WM_LBUTTONUP    : ix = 0; m = tap; up = true; break;\n        case WM_MBUTTONUP    : ix = 1; m = tap; up = true; break;\n        case WM_RBUTTONUP    : ix = 2; m = tap; up = true; break;\n    }\n    if (msg == WM_TIMER) { // long press && double tap\n        for (int i = 0; i < 3; i++) {\n            if (wp == timer_p[i]) {\n                ui_app.mouse = (ui_point_t){ click_at[i].x, click_at[i].y };\n                ui_view.long_press(ui_app.root, i);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", i);\n                ui_timers_done(i);\n            }\n            if (wp == timer_d[i]) {\n//              rt_println(\"timer_p[%d] _d && _p timers done\", i);\n                ui_timers_done(i);\n            }\n        }\n    }\n    if (ix != -1) {\n        ui_app.show_hint(null, -1, -1, 0); // dismiss hint on any click\n        const int32_t double_click_msec = (int32_t)GetDoubleClickTime();\n        const fp64_t  double_click_dt = double_click_msec / 1000.0; // seconds\n//      rt_println(\"double_click_msec: %d double_click_dt: %.3fs\",\n//               double_click_msec, double_click_dt);\n        const int double_click_x = GetSystemMetrics(SM_CXDOUBLECLK) / 2;\n        const int double_click_y = GetSystemMetrics(SM_CYDOUBLECLK) / 2;\n        ui_point_t pt = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };\n        if (m == tap && !up) {\n            swallow = ui_view.tap(ui_app.root, ix, !up);\n            if (ui_app.now  - clicked[ix]  <= double_click_dt &&\n                abs(pt.x - click_at[ix].x) <= double_click_x &&\n                abs(pt.y - click_at[ix].y) <= double_click_y) {\n                ui_app.mouse = (ui_point_t){ click_at[ix].x, click_at[ix].y };\n                ui_view.double_tap(ui_app.root, ix);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix);\n            } else {\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix); // clear timers\n                clicked[ix]  = ui_app.now;\n                click_at[ix] = pt;\n                pressed[ix]  = true;\n//              rt_println(\"clicked[%d] := %.1f %d,%d pressed[%d] := true\",\n//                          ix, clicked[ix], pt.x, pt.y, ix);\n                if ((ui_app_wc.style & CS_DBLCLKS) == 0) {\n                    // only if Windows are not detecting DLBCLKs\n//                  rt_println(\"ui_set_timer(timer_d[%d])\", ix);\n                    ui_set_timer(timer_d[ix], double_click_msec);  // 0.5s\n                }\n                ui_set_timer(timer_p[ix], double_click_msec * 3 / 4); // 0.375s\n            }\n        } else if (up) {\n            fp64_t since_clicked = ui_app.now - clicked[ix];\n//          rt_println(\"pressed[%d]: %d %.3f\", ix, pressed[ix], since_clicked);\n            // only if Windows are not detecting DLBCLKs\n            if ((ui_app_wc.style & CS_DBLCLKS) == 0 &&\n                 pressed[ix] && since_clicked > double_click_dt) {\n                ui_view.double_tap(ui_app.root, ix);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix);\n            }\n            swallow = ui_view.tap(ui_app.root, ix, !up);\n            ui_kill_timer(timer_p[ix]); // long press is not the case\n        } else if (m == double_tap) {\n            rt_assert((ui_app_wc.style & CS_DBLCLKS) != 0);\n            swallow = ui_view.double_tap(ui_app.root, ix);\n            ui_timers_done(ix);\n//          rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n        }\n    }\n    #pragma pop_macro(\"ui_timers_done\")\n    #pragma pop_macro(\"ui_kill_timer\")\n    #pragma pop_macro(\"ui_set_timer\")\n    return swallow;\n}\n\nstatic int64_t ui_app_root_hit_test(const ui_view_t* v, ui_point_t pt) {\n    rt_swear(v == ui_app.root);\n    if (ui_app.no_decor) {\n        rt_assert(ui_app.border.w == ui_app.border.h);\n        // on 96dpi monitors ui_app.border is 1x1\n        // make it easier for the user to resize window\n        int32_t border = rt_max(4, ui_app.border.w * 2);\n        if (ui_app.animating.view != null) {\n            return ui.hit_test.client; // message box or toast is up\n        } else if (!ui_view.is_hidden(&ui_caption.view) &&\n                    ui_view.inside(&ui_caption.view, &pt)) {\n            return ui_caption.view.hit_test(&ui_caption.view, pt);\n        } else if (ui_app.is_maximized()) {\n            int64_t ht = ui_view.hit_test(ui_app.content, pt);\n            return ht == ui.hit_test.nowhere ? ui.hit_test.client : ht;\n        } else if (ui_app.is_full_screen) {\n            return ui.hit_test.client;\n        } else if (pt.x < border && pt.y < border) {\n            return ui.hit_test.top_left;\n        } else if (pt.x > ui_app.crc.w - border && pt.y < border) {\n            return ui.hit_test.top_right;\n        } else if (pt.y < border) {\n            return ui.hit_test.top;\n        } else if (pt.x > ui_app.crc.w - border &&\n                   pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom_right;\n        } else if (pt.x < border && pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom_left;\n        } else if (pt.x < border) {\n            return ui.hit_test.left;\n        } else if (pt.x > ui_app.crc.w - border) {\n            return ui.hit_test.right;\n        } else if (pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom;\n        } else {\n            // drop down to content hit test\n        }\n    }\n    return ui.hit_test.nowhere;\n}\n\nstatic void ui_app_wm_activate(int64_t wp) {\n    bool activate = LOWORD(wp) != WA_INACTIVE;\n    if (!IsWindowVisible(ui_app_window()) && activate) {\n        ui_app.show_window(ui.visibility.restore);\n        SwitchToThisWindow(ui_app_window(), true);\n    }\n    ui_app.request_redraw(); // needed for windows changing active frame color\n}\n\nstatic void ui_app_update_mouse_buttons_state(void) {\n    ui_app.mouse_swapped = GetSystemMetrics(SM_SWAPBUTTON) != 0;\n    ui_app.mouse_left  = (GetAsyncKeyState(ui_app.mouse_swapped ?\n                          VK_RBUTTON : VK_LBUTTON) & 0x8000) != 0;\n    ui_app.mouse_right = (GetAsyncKeyState(ui_app.mouse_swapped ?\n                          VK_LBUTTON : VK_RBUTTON) & 0x8000) != 0;\n}\n\nstatic int64_t ui_app_wm_nc_hit_test(int64_t wp, int64_t lp) {\n    ui_point_t pt = { GET_X_LPARAM(lp) - ui_app.wrc.x,\n                      GET_Y_LPARAM(lp) - ui_app.wrc.y };\n    int64_t ht = ui_view.hit_test(ui_app.root, pt);\n    if (ht != ui.hit_test.nowhere) {\n        return ht;\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_NCHITTEST, wp, lp);\n    }\n}\n\nstatic int64_t ui_app_wm_sys_key_down(int64_t wp, int64_t lp) {\n    ui_app_alt_ctrl_shift(true, wp);\n    if (ui_app_wm_key_pressed(ui_app.root, wp) || wp == VK_MENU) {\n        return 0; // no DefWindowProcW()\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_SYSKEYDOWN, wp, lp);\n    }\n}\n\nstatic void ui_app_wm_set_focus(void) {\n    if (!ui_app.root->state.hidden) {\n        rt_assert(GetActiveWindow() == ui_app_window());\n        if (ui_app.focus != null && ui_app.focus->focus_lost != null) {\n            ui_app.focus->focus_gained(ui_app.focus);\n        }\n    }\n}\n\nstatic void ui_app_wm_kill_focus(void) {\n    if (!ui_app.root->state.hidden &&\n        ui_app.focus != null &&\n        ui_app.focus->focus_lost != null) {\n        ui_app.focus->focus_lost(ui_app.focus);\n    }\n}\n\nstatic int64_t ui_app_wm_nc_calculate_size(int64_t wp, int64_t lp) {\n//  NCCALCSIZE_PARAMS* szp = (NCCALCSIZE_PARAMS*)lp;\n//  rt_println(\"WM_NCCALCSIZE wp: %lld is_max: %d (%d %d %d %d) (%d %d %d %d) (%d %d %d %d)\",\n//      wp, ui_app.is_maximized(),\n//      szp->rgrc[0].left, szp->rgrc[0].top, szp->rgrc[0].right, szp->rgrc[0].bottom,\n//      szp->rgrc[1].left, szp->rgrc[1].top, szp->rgrc[1].right, szp->rgrc[1].bottom,\n//      szp->rgrc[2].left, szp->rgrc[2].top, szp->rgrc[2].right, szp->rgrc[2].bottom);\n    // adjust window client area frame for no_decor windows\n    if (wp == true && ui_app.no_decor && !ui_app.is_maximized()) {\n        return 0;\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_NCCALCSIZE, wp, lp);\n    }\n}\n\nstatic int64_t ui_app_wm_get_dpi_scaled_size(int64_t wp) {\n    // sent before WM_DPICHANGED\n    #ifdef UI_APP_DEBUG\n        int32_t dpi = wp;\n        SIZE* sz = (SIZE*)lp; // in/out\n        ui_point_t cell = { sz->cx, sz->cy };\n        rt_println(\"WM_GETDPISCALEDSIZE dpi %d := %d \"\n            \"size %d,%d *may/must* be adjusted\",\n            ui_app.dpi.window, dpi, cell.x, cell.y);\n    #else\n        (void)wp; // unused\n    #endif\n    if (ui_app_timer_1s_id != 0 && !ui_app.root->state.hidden) {\n        ui_app.request_layout();\n    }\n    // IMPORTANT: return true because:\n    // \"Returning TRUE indicates that a new size has been computed.\n    //  Returning FALSE indicates that the message will not be handled,\n    //  and the default linear DPI scaling will apply to the window.\"\n    // https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-getdpiscaledsize\n    return true;\n}\n\nstatic void ui_app_wm_dpi_changed(void) {\n    ui_app_window_dpi();\n    ui_app_init_fonts(ui_app.dpi.window);\n    if (ui_app_timer_1s_id != 0 && !ui_app.root->state.hidden) {\n        ui_app.request_layout();\n    } else {\n        ui_app_layout_dirty = true;\n    }\n}\n\nstatic bool ui_app_wm_sys_command(int64_t wp, int64_t lp) {\n    uint16_t sys_cmd = (uint16_t)(wp & 0xFF0);\n//  rt_println(\"WM_SYSCOMMAND wp: 0x%08llX lp: 0x%016llX %lld sys: 0x%04X\",\n//          wp, lp, lp, sys_cmd);\n    if (sys_cmd == SC_MINIMIZE && ui_app.hide_on_minimize) {\n        ui_app.show_window(ui.visibility.min_na);\n        ui_app.show_window(ui.visibility.hide);\n    } else  if (sys_cmd == SC_MINIMIZE && ui_app.no_decor) {\n        ui_app.show_window(ui.visibility.min_na);\n    }\n//  if (sys_cmd == SC_KEYMENU) { rt_println(\"SC_KEYMENU lp: %lld\", lp); }\n    // If the selection is in menu handle the key event\n    if (sys_cmd == SC_KEYMENU && lp != 0x20) {\n        return true; // handled: This prevents the error/beep sound\n    }\n    if (sys_cmd == SC_MAXIMIZE && ui_app.no_decor) {\n        return true; // handled: prevent maximizing no decorations window\n    }\n//  if (sys_cmd == SC_MOUSEMENU) {\n//      rt_println(\"SC_KEYMENU.SC_MOUSEMENU 0x%00llX %lld\", wp, lp);\n//  }\n    return false; // drop down to to DefWindowProc\n}\n\nstatic void ui_app_wm_window_position_changing(int64_t wp, int64_t lp) {\n    #ifdef UI_APP_DEBUG // TODO: ui_app.debug.trace.window_position?\n        WINDOWPOS* pos = (WINDOWPOS*)lp;\n        rt_println(\"WM_WINDOWPOSCHANGING flags: 0x%08X\", pos->flags);\n        if (pos->flags & SWP_SHOWWINDOW) {\n            rt_println(\"SWP_SHOWWINDOW\");\n        } else if (pos->flags & SWP_HIDEWINDOW) {\n            rt_println(\"SWP_HIDEWINDOW\");\n        }\n    #else\n        (void)wp; // unused\n        (void)lp; // unused\n    #endif\n}\n\nstatic bool ui_app_wm_mouse(int32_t m, int64_t wp, int64_t lp) {\n    // note: x, y is already in client coordinates\n    ui_app.mouse.x = GET_X_LPARAM(lp);\n    ui_app.mouse.y = GET_Y_LPARAM(lp);\n    return ui_app_mouse(ui_app.root, m, wp);\n}\n\nstatic void ui_app_wm_mouse_wheel(bool vertical, int64_t wp) {\n    if (vertical) {\n        ui_point_t dx_dy = { 0, GET_WHEEL_DELTA_WPARAM(wp) };\n        ui_view.mouse_scroll(ui_app.root, dx_dy);\n    } else {\n        ui_point_t dx_dy = { GET_WHEEL_DELTA_WPARAM(wp), 0 };\n        ui_view.mouse_scroll(ui_app.root, dx_dy);\n    }\n}\n\nstatic void ui_app_wm_input_language_change(uint64_t wp) {\n    #ifdef UI_APP_TRACE_WM_INPUT_LANGUAGE_CHANGE\n    static struct { uint8_t charset; const char* name; } cs[] = {\n        { ANSI_CHARSET       ,     \"ANSI_CHARSET       \" },\n        { DEFAULT_CHARSET    ,     \"DEFAULT_CHARSET    \" },\n        { SYMBOL_CHARSET     ,     \"SYMBOL_CHARSET     \" },\n        { MAC_CHARSET        ,     \"MAC_CHARSET        \" },\n        { SHIFTJIS_CHARSET   ,     \"SHIFTJIS_CHARSET   \" },\n        { HANGEUL_CHARSET    ,     \"HANGEUL_CHARSET    \" },\n        { HANGUL_CHARSET     ,     \"HANGUL_CHARSET     \" },\n        { GB2312_CHARSET     ,     \"GB2312_CHARSET     \" },\n        { CHINESEBIG5_CHARSET,     \"CHINESEBIG5_CHARSET\" },\n        { OEM_CHARSET        ,     \"OEM_CHARSET        \" },\n        { JOHAB_CHARSET      ,     \"JOHAB_CHARSET      \" },\n        { HEBREW_CHARSET     ,     \"HEBREW_CHARSET     \" },\n        { ARABIC_CHARSET     ,     \"ARABIC_CHARSET     \" },\n        { GREEK_CHARSET      ,     \"GREEK_CHARSET      \" },\n        { TURKISH_CHARSET    ,     \"TURKISH_CHARSET    \" },\n        { VIETNAMESE_CHARSET ,     \"VIETNAMESE_CHARSET \" },\n        { THAI_CHARSET       ,     \"THAI_CHARSET       \" },\n        { EASTEUROPE_CHARSET ,     \"EASTEUROPE_CHARSET \" },\n        { RUSSIAN_CHARSET    ,     \"RUSSIAN_CHARSET    \" },\n        { BALTIC_CHARSET     ,     \"BALTIC_CHARSET     \" }\n    };\n    for (int32_t i = 0; i < rt_countof(cs); i++) {\n        if (cs[i].charset == wp) {\n            rt_println(\"WM_INPUTLANGCHANGE: 0x%08X %s\", wp, cs[i].name);\n            break;\n        }\n    }\n    #else\n        (void)wp; // unused\n    #endif\n}\n\nstatic void ui_app_decode_keyboard(int32_t m, int64_t wp, int64_t lp) {\n    // https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags\n    rt_swear(m == WM_KEYDOWN || m == WM_SYSKEYDOWN ||\n          m == WM_KEYUP   || m == WM_SYSKEYUP);\n    uint16_t vk_code   = LOWORD(wp);\n    uint16_t key_flags = HIWORD(lp);\n    uint16_t scan_code = LOBYTE(key_flags);\n    if ((key_flags & KF_EXTENDED) == KF_EXTENDED) {\n        scan_code = MAKEWORD(scan_code, 0xE0);\n    }\n    // previous key-state flag, 1 on autorepeat\n    bool was_key_down = (key_flags & KF_REPEAT) == KF_REPEAT;\n    // repeat count, > 0 if several key down messages was combined into one\n    uint16_t repeat_count = LOWORD(lp);\n    // transition-state flag, 1 on key up\n    bool is_key_released = (key_flags & KF_UP) == KF_UP;\n    // if we want to distinguish these keys:\n    switch (vk_code) {\n        case VK_SHIFT:   // converts to VK_LSHIFT or VK_RSHIFT\n        case VK_CONTROL: // converts to VK_LCONTROL or VK_RCONTROL\n        case VK_MENU:    // converts to VK_LMENU or VK_RMENU\n            vk_code = LOWORD(MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX));\n            break;\n        default: break;\n    }\n    static BYTE keyboard_state[256];\n    uint16_t utf16[3] = {0};\n    rt_fatal_win32err(GetKeyboardState(keyboard_state));\n    // HKL low word Language Identifier\n    //     high word device handle to the physical layout of the keyboard\n    const HKL kl = GetKeyboardLayout(0);\n    // Map virtual key to scan code\n    UINT vk = MapVirtualKeyEx(scan_code, MAPVK_VSC_TO_VK_EX, kl);\n//  rt_println(\"virtual_key: %02X keyboard layout: %08X\",\n//              virtual_key, kl);\n    memset(ui_app_decoded_released, 0x00, sizeof(ui_app_decoded_released));\n    memset(ui_app_decoded_pressed,  0x00, sizeof(ui_app_decoded_pressed));\n    // Translate scan code to character\n    int32_t r = ToUnicodeEx(vk, scan_code, keyboard_state,\n                            utf16, rt_countof(utf16), 0, kl);\n    if (r > 0) {\n        rt_static_assertion(rt_countof(ui_app_decoded_pressed) ==\n                            rt_countof(ui_app_decoded_released));\n        enum { capacity = (int32_t)rt_countof(ui_app_decoded_released) };\n        char* utf8 = is_key_released ?\n            ui_app_decoded_released : ui_app_decoded_pressed;\n        rt_str.utf16to8(utf8, capacity, utf16, -1);\n        if (ui_app_trace_utf16_keyboard_input) {\n            rt_println(\"0x%04X%04X released: %d down: %d repeat: %d \\\"%s\\\"\",\n                    utf16[0], utf16[1], is_key_released, was_key_down,\n                    repeat_count, utf8);\n        }\n    } else if (r == 0) {\n        // The specified virtual key has no translation for the\n        // current state of the keyboard. (E.g. arrows, enter etc)\n    } else {\n        rt_assert(r < 0);\n        // The specified virtual key is a dead key character (accent or diacritic).\n        if (ui_app_trace_utf16_keyboard_input) { rt_println(\"dead key\"); }\n    }\n}\n\nstatic void ui_app_ime_composition(int64_t lp) {\n    if (lp & GCS_RESULTSTR) {\n        HIMC imc = ImmGetContext(ui_app_window());\n        if (imc != null) {\n            char utf8[16];\n            uint16_t utf16[4] = {0};\n            uint32_t bytes = ImmGetCompositionStringW(imc, GCS_RESULTSTR, null, 0);\n            uint32_t count = bytes / sizeof(uint16_t);\n            if (0 < count && count < rt_countof(utf16) - 1) {\n                ImmGetCompositionStringW(imc, GCS_RESULTSTR, utf16, bytes);\n                utf16[count] = 0x00;\n                rt_str.utf16to8(utf8, rt_countof(utf8), utf16, -1);\n                rt_println(\"bytes: %d 0x%04X 0x%04X %s\", bytes, utf16[0], utf16[1], utf8);\n            }\n            rt_fatal_win32err(ImmReleaseContext(ui_app_window(), imc));\n        }\n    }\n}\n\nstatic LRESULT CALLBACK ui_app_window_proc(HWND window, UINT message,\n        WPARAM w_param, LPARAM l_param) {\n    ui_app.now = rt_clock.seconds();\n    if (ui_app.window == null) {\n        ui_app.window = (ui_window_t)window;\n    } else {\n        rt_assert(ui_app_window() == window);\n    }\n    rt_work_queue.dispatch(&ui_app_queue);\n    ui_app_update_wt_timeout(); // because head might have changed\n    const int32_t m  = (int32_t)message;\n    const int64_t wp = (int64_t)w_param;\n    const int64_t lp = (int64_t)l_param;\n    int64_t ret = 0;\n    ui_app_update_mouse_buttons_state();\n    ui_view.lose_hidden_focus(ui_app.root);\n    if (ui_app_click_detector((uint32_t)m, (WPARAM)wp, (LPARAM)lp)) {\n        return 0;\n    }\n    if (ui_view.message(ui_app.root, m, wp, lp, &ret)) {\n        return (LRESULT)ret;\n    }\n    if (m == ui.message.opening) { ui_app_window_opening(); return 0; }\n    if (m == ui.message.closing) { ui_app_window_closing(); return 0; }\n    if (m == ui.message.animate) {\n        ui_app_animate_step((ui_app_animate_function_t)lp, (int32_t)wp, -1);\n        return 0;\n    }\n    ui_app_message_handler_t* handler = ui_app.handlers;\n    while (handler != null) {\n        if (handler->callback(handler, m, wp, lp, &ret)) {\n            return ret;\n        }\n        handler = handler->next;\n    }\n    switch (m) {\n        case WM_GETMINMAXINFO:\n            ui_app_get_min_max_info((MINMAXINFO*)lp);\n            break;\n        case WM_CLOSE        :\n            ui_view.set_focus(null); // before WM_CLOSING\n            ui_app_post_message(ui.message.closing, 0, 0);\n            return 0;\n        case WM_DESTROY      :\n            PostQuitMessage(ui_app.exit_code);\n            break;\n        case WM_ACTIVATE         :\n            ui_app_wm_activate(wp);\n            break;\n        case WM_SYSCOMMAND  :\n            if (ui_app_wm_sys_command(wp, lp)) { return 0; }\n            break;\n        case WM_WINDOWPOSCHANGING:\n            ui_app_wm_window_position_changing(wp, lp);\n            break;\n        case WM_WINDOWPOSCHANGED:\n            ui_app_window_position_changed((WINDOWPOS*)lp);\n            break;\n        case WM_NCHITTEST    :\n            return ui_app_wm_nc_hit_test(wp, lp);\n        case WM_SYSKEYDOWN   :\n            return ui_app_wm_sys_key_down(wp, lp);\n        case WM_SYSCHAR      :\n            if (wp == VK_MENU) { return 0; } // swallow - no DefWindowProc()\n            break;\n        case WM_KEYDOWN      :\n            ui_app_alt_ctrl_shift(true, wp);\n            if (ui_app_wm_key_pressed(ui_app.root, wp)) { return 0; } // swallow\n            break;\n        case WM_SYSKEYUP:\n        case WM_KEYUP        :\n            ui_app_alt_ctrl_shift(false, wp);\n            ui_view.key_released(ui_app.root, wp);\n            break;\n        case WM_TIMER        :\n            ui_app_wm_timer((ui_timer_t)wp);\n            break;\n        case WM_ERASEBKGND   :\n            return true; // no DefWindowProc()\n        case WM_INPUTLANGCHANGE:\n            ui_app_wm_input_language_change(wp);\n            break;\n        case WM_CHAR         :\n            ui_app_wm_char(ui_app.root, (const uint16_t*)&wp);\n            break;\n        case WM_PRINTCLIENT  :\n            ui_app_paint_on_canvas((HDC)wp);\n            break;\n        case WM_SETFOCUS     :\n            ui_app_wm_set_focus();\n            break;\n        case WM_KILLFOCUS    :\n            ui_app_wm_kill_focus();\n            break;\n        case WM_NCCALCSIZE:\n            return ui_app_wm_nc_calculate_size(wp, lp);\n        case WM_PAINT        :\n            ui_app_wm_paint();\n            break;\n        case WM_CONTEXTMENU  :\n            (void)ui_view.context_menu(ui_app.root);\n            break;\n        case WM_THEMECHANGED :\n            ui_theme.refresh();\n            break;\n        case WM_SETTINGCHANGE:\n            ui_app_setting_change((uintptr_t)wp, (uintptr_t)lp);\n            break;\n        case WM_GETDPISCALEDSIZE: // sent before WM_DPICHANGED\n            return ui_app_wm_get_dpi_scaled_size(wp);\n        case WM_DPICHANGED  :\n            ui_app_wm_dpi_changed();\n            break;\n        case WM_NCLBUTTONDOWN   : case WM_NCRBUTTONDOWN  : case WM_NCMBUTTONDOWN  :\n        case WM_NCLBUTTONUP     : case WM_NCRBUTTONUP    : case WM_NCMBUTTONUP    :\n        case WM_NCLBUTTONDBLCLK : case WM_NCRBUTTONDBLCLK: case WM_NCMBUTTONDBLCLK:\n        case WM_NCMOUSEMOVE     :\n            ui_app_nc_mouse_buttons(m, wp, lp);\n            break;\n        case WM_LBUTTONDOWN     : case WM_RBUTTONDOWN  : case WM_MBUTTONDOWN  :\n        case WM_LBUTTONUP       : case WM_RBUTTONUP    : case WM_MBUTTONUP    :\n        case WM_LBUTTONDBLCLK   : case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK:\n//          if (m == WM_LBUTTONDOWN)   { rt_println(\"WM_LBUTTONDOWN\"); }\n//          if (m == WM_LBUTTONUP)     { rt_println(\"WM_LBUTTONUP\"); }\n//          if (m == WM_LBUTTONDBLCLK) { rt_println(\"WM_LBUTTONDBLCLK\"); }\n            if (ui_app_wm_mouse(m, wp, lp)) { return 0; }\n            break;\n        case WM_MOUSEHOVER      :\n        case WM_MOUSEMOVE       :\n            if (ui_app_wm_mouse(m, wp, lp)) { return 0; }\n            break;\n        case WM_MOUSEWHEEL   :\n            ui_app_wm_mouse_wheel(true, wp);\n            break;\n        case WM_MOUSEHWHEEL  :\n            ui_app_wm_mouse_wheel(false, wp);\n            break;\n        // debugging:\n        #ifdef UI_APP_DEBUGING_ALT_KEYBOARD_SHORTCUTS\n        case WM_PARENTNOTIFY  : rt_println(\"WM_PARENTNOTIFY\");     break;\n        case WM_ENTERMENULOOP : rt_println(\"WM_ENTERMENULOOP\");    return 0;\n        case WM_EXITMENULOOP  : rt_println(\"WM_EXITMENULOOP\");     return 0;\n        case WM_INITMENU      : rt_println(\"WM_INITMENU\");         return 0;\n        case WM_MENUCHAR      : rt_println(\"WM_MENUCHAR\");         return MNC_CLOSE << 16;\n        case WM_CAPTURECHANGED: rt_println(\"WM_CAPTURECHANGED\");   break;\n        case WM_MENUSELECT    : rt_println(\"WM_MENUSELECT\");       return 0;\n        #else\n        // ***Important***: prevents annoying beeps on Alt+Shortcut\n        case WM_MENUCHAR      : return MNC_CLOSE << 16;\n        // TODO: may be beeps are good if no UI controls reacted\n        #endif\n        // TODO: investigate WM_SETCURSOR in regards to wait cursor\n        case WM_SETCURSOR    :\n            if (LOWORD(lp) == HTCLIENT) { // see WM_NCHITTEST\n                SetCursor((HCURSOR)ui_app.cursor);\n                return true; // must NOT call DefWindowProc()\n            }\n            break;\n#ifdef UI_APP_USE_WM_IME\n        case WM_IME_CHAR:\n            rt_println(\"WM_IME_CHAR: 0x%04X\", wp);\n            break;\n        case WM_IME_NOTIFY:\n            rt_println(\"WM_IME_NOTIFY\");\n            break;\n        case WM_IME_REQUEST:\n            rt_println(\"WM_IME_REQUEST\");\n            break;\n        case WM_IME_STARTCOMPOSITION:\n            rt_println(\"WM_IME_STARTCOMPOSITION\");\n            break;\n        case WM_IME_ENDCOMPOSITION:\n            rt_println(\"WM_IME_ENDCOMPOSITION\");\n            break;\n        case WM_IME_COMPOSITION:\n            rt_println(\"WM_IME_COMPOSITION\");\n            ui_app_ime_composition(lp);\n            break;\n#endif  // UI_APP_USE_WM_IME\n        // TODO:\n        case WM_UNICHAR       : // only UTF-32 via PostMessage?\n            rt_println(\"???\");\n            // see: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input\n            // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-tounicode\n            break;\n        default:\n            break;\n    }\n    return DefWindowProcW(ui_app_window(), (UINT)m, (WPARAM)wp, lp);\n}\n\nstatic long ui_app_get_window_long(int32_t index) {\n    rt_core.set_err(0);\n    long v = GetWindowLongA(ui_app_window(), index);\n    rt_fatal_if_error(rt_core.err());\n    return v;\n}\n\nstatic long ui_app_set_window_long(int32_t index, long value) {\n    rt_core.set_err(0);\n    long r = SetWindowLongA(ui_app_window(), index, value); // r previous value\n    rt_fatal_if_error(rt_core.err());\n    return r;\n}\n\nstatic void ui_app_modify_window_style(uint32_t include, uint32_t exclude) {\n    long s = ui_app_get_window_long(GWL_STYLE);\n    s &= ~exclude;\n    s |=  include;\n    ui_app_set_window_long(GWL_STYLE, s);\n}\n\nstatic DWORD ui_app_window_style(void) {\n    return ui_app.no_decor ? WS_POPUPWINDOW|\n                             WS_THICKFRAME|\n                             WS_MINIMIZEBOX\n                           : WS_OVERLAPPEDWINDOW;\n}\n\nstatic errno_t ui_app_set_layered_window(ui_color_t color, fp32_t alpha) {\n    uint8_t  a = 0; // alpha 0..255\n    uint32_t c = 0; // R8G8B8\n    DWORD mask = 0;\n    if (0 <= alpha && alpha <= 1.0f) {\n        mask |= LWA_ALPHA;\n        a = (uint8_t)(alpha * 255 + 0.5f);\n    }\n    if (color != ui_color_undefined) {\n        mask |= LWA_COLORKEY;\n        rt_assert(ui_color_is_8bit(color));\n        c = ui_gdi.color_rgb(color);\n    }\n    return rt_b2e(SetLayeredWindowAttributes(ui_app_window(), c, a, mask));\n}\n\nstatic void ui_app_set_dwm_attribute(uint32_t mode, void* a, DWORD bytes) {\n    rt_fatal_if_error(DwmSetWindowAttribute(ui_app_window(), mode, a, bytes));\n}\n\nstatic void ui_app_init_dwm(void) {\n    if (IsWindowsVersionOrGreater(10, 0, 22000)) {\n        // do not call on Win10 - will fail\n        DWM_WINDOW_CORNER_PREFERENCE c = DWMWCP_ROUND;\n        ui_app_set_dwm_attribute(DWMWA_WINDOW_CORNER_PREFERENCE, &c, sizeof(c));\n        COLORREF cc = (COLORREF)ui_gdi.color_rgb(ui_color_rgb(45, 45, 48));\n        ui_app_set_dwm_attribute(DWMWA_CAPTION_COLOR, &cc, sizeof(cc));\n    }\n    BOOL e = true; // must be 32-bit BOOL because of sizeof()\n    ui_app_set_dwm_attribute(DWMWA_USE_IMMERSIVE_DARK_MODE, &e, sizeof(e));\n    // kudos for double negatives - so easy to make mistakes:\n    ui_app_set_dwm_attribute(DWMWA_TRANSITIONS_FORCEDISABLED, &e, sizeof(e));\n    enum DWMNCRENDERINGPOLICY rp = DWMNCRP_USEWINDOWSTYLE;\n    ui_app_set_dwm_attribute(DWMWA_NCRENDERING_POLICY, &rp, sizeof(rp));\n    if (ui_app.no_decor) {\n        ui_app_set_dwm_attribute(DWMWA_ALLOW_NCPAINT, &e, sizeof(e));\n        MARGINS margins = { 0, 0, 0, 0 };\n        rt_fatal_if_error(\n            DwmExtendFrameIntoClientArea(ui_app_window(), &margins)\n        );\n    }\n}\n\nstatic void ui_app_swp(HWND top, int32_t x, int32_t y, int32_t w, int32_t h,\n        uint32_t f) {\n    rt_fatal_win32err(SetWindowPos(ui_app_window(), top, x, y, w, h, f));\n}\n\nstatic void ui_app_swp_flags(uint32_t f) {\n    rt_fatal_win32err(SetWindowPos(ui_app_window(), null, 0, 0, 0, 0, f));\n}\n\nstatic void ui_app_disable_sys_menu_item(HMENU sys_menu, uint32_t item) {\n    const uint32_t f = MF_BYCOMMAND | MF_DISABLED;\n    rt_fatal_win32err(EnableMenuItem(sys_menu, item, f));\n}\n\nstatic void ui_app_init_sys_menu(void) {\n    // tried to remove unused items from system menu which leads to\n    // AllowDarkModeForWindow() failed 0x000005B0(1456) \"A menu item was not found.\"\n    // SetPreferredAppMode() failed 0x000005B0(1456) \"A menu item was not found.\"\n    // this is why they just disabled instead.\n    HMENU sys_menu = GetSystemMenu(ui_app_window(), false);\n    rt_not_null(sys_menu);\n    if (ui_app.no_min || ui_app.no_max) {\n        int32_t exclude = WS_SIZEBOX;\n        if (ui_app.no_min) { exclude = WS_MINIMIZEBOX; }\n        if (ui_app.no_max) { exclude = WS_MAXIMIZEBOX; }\n        ui_app_modify_window_style(0, exclude);\n        if (ui_app.no_min) { ui_app_disable_sys_menu_item(sys_menu, SC_MINIMIZE); }\n        if (ui_app.no_max) { ui_app_disable_sys_menu_item(sys_menu, SC_MAXIMIZE); }\n    }\n    if (ui_app.no_size) {\n        ui_app_disable_sys_menu_item(sys_menu, SC_SIZE);\n        ui_app_modify_window_style(0, WS_SIZEBOX);\n        const uint32_t f = SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |\n                           SWP_NOACTIVATE;\n        ui_app_swp_flags(f);\n    }\n}\n\nstatic void ui_app_create_window(const ui_rect_t r) {\n    uint16_t class_name[256];\n    rt_str.utf8to16(class_name, rt_countof(class_name), ui_app.class_name, -1);\n    WNDCLASSW* wc = &ui_app_wc;\n    // CS_DBLCLKS no longer needed. Because code detects long-press\n    // it does double click too. Editor uses both for word and paragraph select.\n    wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_SAVEBITS;\n    wc->lpfnWndProc = ui_app_window_proc;\n    wc->cbClsExtra = 0;\n    wc->cbWndExtra = 256 * 1024;\n    wc->hInstance = GetModuleHandleA(null);\n    wc->hIcon = (HICON)ui_app.icon;\n    wc->hCursor = (HCURSOR)ui_app.cursor;\n    wc->hbrBackground = null;\n    wc->lpszMenuName = null;\n    wc->lpszClassName = class_name;\n    ATOM atom = RegisterClassW(wc);\n    rt_fatal_if(atom == 0);\n    uint16_t title[256];\n    rt_str.utf8to16(title, rt_countof(title), ui_app.title, -1);\n    HWND window = CreateWindowExW(WS_EX_COMPOSITED | WS_EX_LAYERED,\n        class_name, title, ui_app_window_style(),\n        r.x, r.y, r.w, r.h, null, null, wc->hInstance, null);\n    rt_not_null(ui_app.window);\n    rt_swear(window == ui_app_window());\n    ui_app.show_window(ui.visibility.hide);\n    ui_view.set_text(&ui_caption.title, \"%s\", ui_app.title);\n    ui_app.dpi.window = (int32_t)GetDpiForWindow(ui_app_window());\n    RECT wrc = ui_app_ui2rect(&r);\n    rt_fatal_win32err(GetWindowRect(ui_app_window(), &wrc));\n    ui_app.wrc = ui_app_rect2ui(&wrc);\n    ui_app_init_dwm();\n    ui_app_init_sys_menu();\n    ui_theme.refresh();\n    if (ui_app.visibility != ui.visibility.hide) {\n        AnimateWindow(ui_app_window(), 250, AW_ACTIVATE);\n        ui_app.show_window(ui_app.visibility);\n        ui_app_update_crc();\n    }\n    // even if it is hidden:\n    ui_app_post_message(ui.message.opening, 0, 0);\n//  SetWindowTheme(ui_app_window(), L\"DarkMode_Explorer\", null); ???\n}\n\nstatic void ui_app_full_screen(bool on) {\n    static long style;\n    static WINDOWPLACEMENT wp;\n    if (on != ui_app.is_full_screen) {\n        ui_app_show_task_bar(!on);\n        if (on) {\n            ui_app_modify_window_style(0, WS_OVERLAPPEDWINDOW|WS_POPUPWINDOW);\n            ui_app_modify_window_style(WS_POPUP | WS_VISIBLE, 0);\n            wp.length = sizeof(wp);\n            rt_fatal_win32err(GetWindowPlacement(ui_app_window(), &wp));\n            WINDOWPLACEMENT nwp = wp;\n            nwp.showCmd = SW_SHOWNORMAL;\n            nwp.rcNormalPosition = (RECT){ui_app.mrc.x, ui_app.mrc.y,\n                ui_app.mrc.x + ui_app.mrc.w, ui_app.mrc.y + ui_app.mrc.h};\n            rt_fatal_win32err(SetWindowPlacement(ui_app_window(), &nwp));\n        } else {\n            rt_fatal_win32err(SetWindowPlacement(ui_app_window(), &wp));\n            ui_app_set_window_long(GWL_STYLE, ui_app_window_style());\n            enum { flags = SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |\n                           SWP_NOZORDER | SWP_NOOWNERZORDER };\n            ui_app_swp_flags(flags);\n        }\n        ui_app.is_full_screen = on;\n    }\n}\n\nstatic bool ui_app_set_focus(ui_view_t* rt_unused(v)) { return false; }\n\nstatic void ui_app_request_redraw(void) {  // < 2us\n    SetEvent(ui_app_event_invalidate);\n}\n\nstatic void ui_app_draw(void) {\n    rt_println(\"avoid at all cost. bad performance, bad UX\");\n    UpdateWindow(ui_app_window());\n}\n\nstatic void ui_app_invalidate_rect(const ui_rect_t* r) {\n    RECT rc = ui_app_ui2rect(r);\n    InvalidateRect(ui_app_window(), &rc, false);\n//  rt_backtrace_here();\n}\n\nstatic int32_t ui_app_message_loop(void) {\n    MSG msg = {0};\n    while (GetMessageW(&msg, null, 0, 0)) {\n        if (msg.message == WM_KEYDOWN    || msg.message == WM_KEYUP ||\n            msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) {\n            // before TranslateMessage():\n            ui_app_decode_keyboard(msg.message, msg.wParam, msg.lParam);\n        }\n        TranslateMessage(&msg);\n        DispatchMessageW(&msg);\n    }\n    rt_work_queue.flush(&ui_app_queue);\n    rt_assert(msg.message == WM_QUIT);\n    return (int32_t)msg.wParam;\n}\n\nstatic void ui_app_dispose(void) {\n    ui_app_dispose_fonts();\n    rt_event.dispose(ui_app_event_invalidate);\n    ui_app_event_invalidate = null;\n}\n\nstatic void ui_app_cursor_set(ui_cursor_t c) {\n    // https://docs.microsoft.com/en-us/windows/win32/menurc/using-cursors\n    ui_app.cursor = c;\n    SetClassLongPtr(ui_app_window(), GCLP_HCURSOR, (LONG_PTR)c);\n    POINT pt = {0};\n    if (GetCursorPos(&pt)) { SetCursorPos(pt.x + 1, pt.y); SetCursorPos(pt.x, pt.y); }\n}\n\nstatic void ui_app_close_window(void) {\n    // TODO: fix me. Band aid - start up with maximized no_decor window is broken\n    if (ui_app.is_maximized()) { ui_app.show_window(ui.visibility.restore); }\n    ui_app_post_message(WM_CLOSE, 0, 0);\n}\n\nstatic void ui_app_quit(int32_t exit_code) {\n    ui_app.exit_code = exit_code;\n    if (ui_app.can_close != null) {\n        (void)ui_app.can_close(); // and deliberately ignore result\n    }\n    ui_app.can_close = null; // will not be called again\n    ui_app.close(); // close and destroy app only window\n}\n\nstatic void ui_app_show_hint_or_toast(ui_view_t* v, int32_t x, int32_t y,\n        fp64_t timeout) {\n    if (v != null) {\n        ui_app.animating.x = x;\n        ui_app.animating.y = y;\n        ui_app.animating.focused = ui_app.focus;\n        if (v->type == ui_view_mbx) {\n            ((ui_mbx_t*)v)->option = -1;\n            if (v->focusable) {\n                 ui_view.set_focus(v);\n            }\n        }\n        // allow unparented ui for toast and hint\n        ui_view_call_init(v);\n        const int32_t steps = x < 0 && y < 0 ? ui_app_animation_steps : 1;\n        ui_app_animate_start(ui_app_toast_dim, steps);\n        ui_app.animating.view = v;\n        v->parent = ui_app.root;\n        if (v->focusable) { ui_view.set_focus(v); }\n        ui_app.animating.time = timeout > 0 ? ui_app.now + timeout : 0;\n    } else {\n        ui_app_toast_cancel();\n    }\n}\n\nstatic void ui_app_show_toast(ui_view_t* view, fp64_t timeout) {\n    ui_app_show_hint_or_toast(view, -1, -1, timeout);\n}\n\nstatic void ui_app_show_hint(ui_view_t* view, int32_t x, int32_t y,\n        fp64_t timeout) {\n    if (view != null) {\n        ui_app_show_hint_or_toast(view, x, y, timeout);\n    } else if (ui_app.animating.view != null && ui_app.animating.x >= 0 &&\n               ui_app.animating.y >= 0) {\n        ui_app_toast_cancel(); // only cancel hints not toasts\n    }\n}\n\nstatic void ui_app_formatted_toast_va(fp64_t timeout, const char* format, va_list va) {\n    ui_app_show_toast(null, 0);\n    static ui_label_t label = ui_label(0.0, \"\");\n    ui_label_init_va(&label, 0.0, format, va);\n    ui_app_show_toast(&label, timeout);\n}\n\nstatic void ui_app_formatted_toast(fp64_t timeout, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_app_formatted_toast_va(timeout, format, va);\n    va_end(va);\n}\n\nstatic int32_t ui_app_caret_w;\nstatic int32_t ui_app_caret_h;\nstatic int32_t ui_app_caret_x = -1;\nstatic int32_t ui_app_caret_y = -1;\nstatic bool    ui_app_caret_shown;\n\nstatic void ui_app_create_caret(int32_t w, int32_t h) {\n    ui_app_caret_w = w;\n    ui_app_caret_h = h;\n    rt_fatal_win32err(CreateCaret(ui_app_window(), null, w, h));\n    rt_assert(GetSystemMetrics(SM_CARETBLINKINGENABLED));\n}\n\nstatic void ui_app_invalidate_caret(void) {\n    if (ui_app_caret_w >  0 && ui_app_caret_h >  0 &&\n        ui_app_caret_x >= 0 && ui_app_caret_y >= 0 &&\n        ui_app_caret_shown) {\n        RECT rc = { ui_app_caret_x, ui_app_caret_y,\n                    ui_app_caret_x + ui_app_caret_w,\n                    ui_app_caret_y + ui_app_caret_h };\n        rt_fatal_win32err(InvalidateRect(ui_app_window(), &rc, false));\n    }\n}\n\nstatic void ui_app_show_caret(void) {\n    rt_assert(!ui_app_caret_shown);\n    rt_fatal_win32err(ShowCaret(ui_app_window()));\n    ui_app_caret_shown = true;\n    ui_app_invalidate_caret();\n}\n\nstatic void ui_app_move_caret(int32_t x, int32_t y) {\n    ui_app_invalidate_caret(); // where is was\n    ui_app_caret_x = x;\n    ui_app_caret_y = y;\n    rt_fatal_win32err(SetCaretPos(x, y));\n    ui_app_invalidate_caret(); // where it is now\n}\n\nstatic void ui_app_hide_caret(void) {\n    rt_assert(ui_app_caret_shown);\n    rt_fatal_win32err(HideCaret(ui_app_window()));\n    ui_app_invalidate_caret();\n    ui_app_caret_shown = false;\n}\n\nstatic void ui_app_destroy_caret(void) {\n    ui_app_caret_w = 0;\n    ui_app_caret_h = 0;\n    rt_fatal_win32err(DestroyCaret());\n}\n\nstatic void ui_app_beep(int32_t kind) {\n    static int32_t beep_id[] = { MB_OK, MB_ICONINFORMATION, MB_ICONQUESTION,\n                          MB_ICONWARNING, MB_ICONERROR};\n    rt_swear(0 <= kind && kind < rt_countof(beep_id));\n    rt_fatal_win32err(MessageBeep(beep_id[kind]));\n}\n\nstatic void ui_app_enable_sys_command_close(void) {\n    EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false),\n        SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);\n}\n\nstatic void ui_app_console_disable_close(void) {\n    EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false),\n        SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);\n    (void)freopen(\"CONOUT$\", \"w\", stdout);\n    (void)freopen(\"CONOUT$\", \"w\", stderr);\n    atexit(ui_app_enable_sys_command_close);\n}\n\nstatic int ui_app_console_attach(void) {\n    int r = AttachConsole(ATTACH_PARENT_PROCESS) ? 0 : rt_core.err();\n    if (r == 0) {\n        ui_app_console_disable_close();\n        rt_thread.sleep_for(0.1); // give cmd.exe a chance to print prompt again\n        printf(\"\\n\");\n    }\n    return r;\n}\n\nstatic bool ui_app_is_stdout_redirected(void) {\n    // https://stackoverflow.com/questions/30126490/how-to-check-if-stdout-is-redirected-to-a-file-or-to-a-console\n    HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);\n    DWORD type = out == null ? FILE_TYPE_UNKNOWN : GetFileType(out);\n    type &= ~(DWORD)FILE_TYPE_REMOTE;\n    // FILE_TYPE_DISK or FILE_TYPE_CHAR or FILE_TYPE_PIPE\n    return type != FILE_TYPE_UNKNOWN;\n}\n\nstatic bool ui_app_is_console_visible(void) {\n    HWND cw = GetConsoleWindow();\n    return cw != null && IsWindowVisible(cw);\n}\n\nstatic int ui_app_set_console_size(int16_t w, int16_t h) {\n    // width/height in characters\n    HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n    CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n    int r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    if (r != 0) {\n        rt_println(\"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    } else {\n        // tricky because correct order of the calls\n        // SetConsoleWindowInfo() SetConsoleScreenBufferSize() depends on\n        // current Window Size (in pixels) ConsoleWindowSize(in characters)\n        // and SetConsoleScreenBufferSize().\n        // After a lot of experimentation and reading docs most sensible option\n        // is to try both calls in two different orders.\n        COORD c = {w, h};\n        SMALL_RECT const min_win = { 0, 0, c.X - 1, c.Y - 1 };\n        c.Y = 9001; // maximum buffer number of rows at the moment of implementation\n        int r0 = SetConsoleWindowInfo(console, true, &min_win) ? 0 : rt_core.err();\n//      if (r0 != 0) { rt_println(\"SetConsoleWindowInfo() %s\", rt_strerr(r0)); }\n        int r1 = SetConsoleScreenBufferSize(console, c) ? 0 : rt_core.err();\n//      if (r1 != 0) { rt_println(\"SetConsoleScreenBufferSize() %s\", rt_strerr(r1)); }\n        if (r0 != 0 || r1 != 0) { // try in reverse order (which expected to work):\n            r0 = SetConsoleScreenBufferSize(console, c) ? 0 : rt_core.err();\n            if (r0 != 0) { rt_println(\"SetConsoleScreenBufferSize() %s\", rt_strerr(r0)); }\n            r1 = SetConsoleWindowInfo(console, true, &min_win) ? 0 : rt_core.err();\n            if (r1 != 0) { rt_println(\"SetConsoleWindowInfo() %s\", rt_strerr(r1)); }\n\t    }\n        r = r0 == 0 ? r1 : r0; // first of two errors\n    }\n    return r;\n}\n\nstatic void ui_app_console_largest(void) {\n    HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n    // User have to manual uncheck \"[x] Let system position window\" in console\n    // Properties -> Layout -> Window Position because I did not find the way\n    // to programmatically unchecked it.\n    // commented code below does not work.\n    // see: https://www.os2museum.com/wp/disabling-quick-edit-mode/\n    // and: https://learn.microsoft.com/en-us/windows/console/setconsolemode\n    /* DOES NOT WORK:\n    DWORD mode = 0;\n    r = GetConsoleMode(console, &mode) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleMode() %s\", rt_strerr(r));\n    mode &= ~ENABLE_AUTO_POSITION;\n    r = SetConsoleMode(console, &mode) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"SetConsoleMode() %s\", rt_strerr(r));\n    */\n    CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n    int r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    COORD c = GetLargestConsoleWindowSize(console);\n    if (c.X > 80) { c.X &= ~0x7; }\n    if (c.Y > 24) { c.Y &= ~0x3; }\n    if (c.X > 80) { c.X -= 8; }\n    if (c.Y > 24) { c.Y -= 4; }\n    ui_app_set_console_size(c.X, c.Y);\n    r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    info.dwSize.Y = 9999; // maximum value at the moment of implementation\n    r = SetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"SetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    ui_app_save_console_pos();\n}\n\nstatic void ui_app_make_topmost(void) {\n    //  Places the window above all non-topmost windows.\n    // The window maintains its topmost position even when it is deactivated.\n    enum { swp = SWP_SHOWWINDOW | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOSIZE };\n    ui_app_swp(HWND_TOPMOST, 0, 0, 0, 0, swp);\n}\n\nstatic void ui_app_activate(void) {\n    rt_core.set_err(0);\n    HWND previous = SetActiveWindow(ui_app_window());\n    if (previous == null) { rt_fatal_if_error(rt_core.err()); }\n}\n\nstatic void ui_app_bring_to_foreground(void) {\n    // SetForegroundWindow() does not activate window:\n    rt_fatal_win32err(SetForegroundWindow(ui_app_window()));\n}\n\nstatic void ui_app_bring_to_front(void) {\n    ui_app.bring_to_foreground();\n    ui_app.make_topmost();\n    ui_app.bring_to_foreground();\n    // because bring_to_foreground() does not activate\n    ui_app.activate();\n    ui_app.request_focus();\n}\n\nstatic void ui_app_set_title(const char* title) {\n    ui_view.set_text(&ui_caption.title, \"%s\", title);\n    rt_fatal_win32err(SetWindowTextA(ui_app_window(), rt_nls.str(title)));\n}\n\nstatic void ui_app_capture_mouse(bool on) {\n    static int32_t mouse_capture;\n    if (on) {\n        rt_swear(mouse_capture == 0);\n        mouse_capture++;\n        SetCapture(ui_app_window());\n    } else {\n        rt_swear(mouse_capture == 1);\n        mouse_capture--;\n        ReleaseCapture();\n    }\n}\n\nstatic void ui_app_move_and_resize(const ui_rect_t* rc) {\n    enum { swp = SWP_NOZORDER | SWP_NOACTIVATE };\n    ui_app_swp(null, rc->x, rc->y, rc->w, rc->h, swp);\n}\n\nstatic void ui_app_set_console_title(HWND cw) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    static char text[256];\n    text[0] = 0;\n    GetWindowTextA((HWND)ui_app.window, text, rt_countof(text));\n    text[rt_countof(text) - 1] = 0;\n    char title[256];\n    rt_str_printf(title, \"%s - Console\", text);\n    rt_fatal_win32err(SetWindowTextA(cw, title));\n}\n\nstatic void ui_app_restore_console(int32_t *visibility) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null) {\n        RECT wr = {0};\n        GetWindowRect(cw, &wr);\n        ui_rect_t rc = ui_app_rect2ui(&wr);\n        ui_app_load_console_pos(&rc, visibility);\n        if (rc.w > 0 && rc.h > 0) {\n//          rt_println(\"%d,%d %dx%d px\", rc.x, rc.y, rc.w, rc.h);\n            CONSOLE_SCREEN_BUFFER_INFOEX info = {\n                sizeof(CONSOLE_SCREEN_BUFFER_INFOEX)\n            };\n            int32_t r = rt_config.load(ui_app.class_name,\n                \"console_screen_buffer_infoex\", &info, (int32_t)sizeof(info));\n            if (r == sizeof(info)) { // 24x80\n                SMALL_RECT sr = info.srWindow;\n                int16_t w = (int16_t)rt_max(sr.Right - sr.Left + 1, 80);\n                int16_t h = (int16_t)rt_max(sr.Bottom - sr.Top + 1, 24);\n//              rt_println(\"info: %dx%d\", info.dwSize.X, info.dwSize.Y);\n//              rt_println(\"%d,%d %dx%d\", sr.Left, sr.Top, w, h);\n                if (w > 0 && h > 0) { ui_app_set_console_size(w, h); }\n    \t    }\n            // do not resize console window just restore it's position\n            enum { flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE };\n            rt_fatal_win32err(SetWindowPos(cw, null,\n                    rc.x, rc.y, rc.w, rc.h, flags));\n        } else {\n            ui_app_console_largest();\n        }\n    }\n}\n\nstatic void ui_app_console_show(bool b) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null && b != ui_app.is_console_visible()) {\n        if (ui_app.is_console_visible()) { ui_app_save_console_pos(); }\n        if (b) {\n            int32_t ignored_visibility = 0;\n            ui_app_restore_console(&ignored_visibility);\n            ui_app_set_console_title(cw);\n        }\n        // If the window was previously visible, the return value is nonzero.\n        // If the window was previously hidden, the return value is zero.\n        bool unused_was_visible = ShowWindow(cw, b ? SW_SHOWNOACTIVATE : SW_HIDE);\n        (void)unused_was_visible;\n        if (b) { InvalidateRect(cw, null, true); SetActiveWindow(cw); }\n        ui_app_save_console_pos(); // again after visibility changed\n    }\n}\n\nstatic int ui_app_console_create(void) {\n    int r = AllocConsole() ? 0 : rt_core.err();\n    if (r == 0) {\n        ui_app_console_disable_close();\n        int32_t visibility = 0;\n        ui_app_restore_console(&visibility);\n        ui_app.console_show(visibility != 0);\n    }\n    return r;\n}\n\nstatic fp32_t ui_app_px2in(int32_t pixels) {\n    rt_assert(ui_app.dpi.monitor_max > 0);\n//  rt_println(\"ui_app.dpi.monitor_raw: %d\", ui_app.dpi.monitor_max);\n    return ui_app.dpi.monitor_max > 0 ?\n           (fp32_t)pixels / (fp32_t)ui_app.dpi.monitor_max : 0;\n}\n\nstatic int32_t ui_app_in2px(fp32_t inches) {\n    rt_assert(ui_app.dpi.monitor_max > 0);\n//  rt_println(\"ui_app.dpi.monitor_raw: %d\", ui_app.dpi.monitor_max);\n    return (int32_t)(inches * (fp64_t)ui_app.dpi.monitor_max + 0.5);\n}\n\nstatic void ui_app_request_layout(void) {\n    ui_app_layout_dirty = true;\n    ui_app.request_redraw();\n}\n\nstatic void ui_app_show_window(int32_t show) {\n    rt_assert(ui.visibility.hide <= show &&\n           show <= ui.visibility.force_min);\n    // ShowWindow() does not have documented error reporting\n    bool was_visible = ShowWindow(ui_app_window(), show);\n    (void)was_visible;\n    const bool hiding =\n        show == ui.visibility.hide ||\n        show == ui.visibility.minimize ||\n        show == ui.visibility.show_na ||\n        show == ui.visibility.min_na;\n    if (!hiding) {\n        ui_app.bring_to_foreground(); // this does not make it ActiveWindow\n        enum { flags = SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOSIZE |\n                       SWP_NOREPOSITION | SWP_NOMOVE };\n        ui_app_swp_flags(flags);\n        ui_app.request_focus();\n    } else if (show == ui.visibility.hide ||\n               show == ui.visibility.minimize ||\n               show == ui.visibility.min_na) {\n        ui_app_toast_cancel();\n    }\n}\n\nstatic const char* ui_app_open_file(const char* folder,\n        const char* pairs[], int32_t n) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_assert(pairs == null && n == 0 || n >= 2 && n % 2 == 0);\n    static uint16_t memory[4 * 1024];\n    uint16_t* filter = memory;\n    if (pairs == null || n == 0) {\n        filter = L\"All Files\\0*\\0\\0\";\n    } else {\n        int32_t left = rt_countof(memory) - 2;\n        uint16_t* s = memory;\n        for (int32_t i = 0; i < n; i+= 2) {\n            uint16_t* s0 = s;\n            rt_str.utf8to16(s0, left, pairs[i + 0], -1);\n            int32_t n0 = (int32_t)rt_str.len16(s0);\n            rt_assert(n0 > 0);\n            s += n0 + 1;\n            left -= n0 + 1;\n            uint16_t* s1 = s;\n            rt_str.utf8to16(s1, left, pairs[i + 1], -1);\n            int32_t n1 = (int32_t)rt_str.len16(s1);\n            rt_assert(n1 > 0);\n            s[n1] = 0;\n            s += n1 + 1;\n            left -= n1 + 1;\n        }\n        *s++ = 0;\n    }\n    static uint16_t dir[rt_files_max_path];\n    dir[0] = 0;\n    rt_str.utf8to16(dir, rt_countof(dir), folder, -1);\n    static uint16_t path[rt_files_max_path];\n    path[0] = 0;\n    OPENFILENAMEW ofn = { sizeof(ofn) };\n    ofn.hwndOwner = (HWND)ui_app.window;\n    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;\n    ofn.lpstrFilter = filter;\n    ofn.lpstrInitialDir = dir;\n    ofn.lpstrFile = path;\n    ofn.nMaxFile = sizeof(path);\n    static rt_file_name_t fn;\n    fn.s[0] = 0;\n    if (GetOpenFileNameW(&ofn) && path[0] != 0) {\n        rt_str.utf16to8(fn.s, rt_countof(fn.s), path, -1);\n    } else {\n        fn.s[0] = 0;\n    }\n    return fn.s;\n}\n\n// TODO: use clipboard instead?\n\nstatic errno_t ui_app_clipboard_put_image(ui_bitmap_t* im) {\n    HDC canvas = GetDC(null);\n    rt_not_null(canvas);\n    HDC src = CreateCompatibleDC(canvas); rt_not_null(src);\n    HDC dst = CreateCompatibleDC(canvas); rt_not_null(dst);\n    // CreateCompatibleBitmap(dst) will create monochrome bitmap!\n    // CreateCompatibleBitmap(canvas) will create display compatible\n    HBITMAP texture = CreateCompatibleBitmap(canvas, im->w, im->h);\n    rt_not_null(texture);\n    HBITMAP s = SelectBitmap(src, im->texture); rt_not_null(s);\n    HBITMAP d = SelectBitmap(dst, texture);     rt_not_null(d);\n    POINT pt = { 0 };\n    rt_fatal_win32err(SetBrushOrgEx(dst, 0, 0, &pt));\n    rt_fatal_win32err(StretchBlt(dst, 0, 0, im->w, im->h, src, 0, 0,\n        im->w, im->h, SRCCOPY));\n    errno_t r = rt_b2e(OpenClipboard(GetDesktopWindow()));\n    if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n    if (r == 0) {\n        r = rt_b2e(EmptyClipboard());\n        if (r != 0) { rt_println(\"EmptyClipboard() failed %s\", rt_strerr(r)); }\n    }\n    if (r == 0) {\n        r = rt_b2e(SetClipboardData(CF_BITMAP, texture));\n        if (r != 0) {\n            rt_println(\"SetClipboardData() failed %s\", rt_strerr(r));\n        }\n    }\n    if (r == 0) {\n        r = rt_b2e(CloseClipboard());\n        if (r != 0) {\n            rt_println(\"CloseClipboard() failed %s\", rt_strerr(r));\n        }\n    }\n    rt_not_null(SelectBitmap(dst, d));\n    rt_not_null(SelectBitmap(src, s));\n    rt_fatal_win32err(DeleteBitmap(texture));\n    rt_fatal_win32err(DeleteDC(dst));\n    rt_fatal_win32err(DeleteDC(src));\n    rt_fatal_win32err(ReleaseDC(null, canvas));\n    return r;\n}\n\nstatic ui_view_t ui_app_view = ui_view(list);\nstatic ui_view_t ui_app_content = ui_view(stack);\n\nstatic bool ui_app_is_active(void) { return GetActiveWindow() == ui_app_window(); }\n\nstatic bool ui_app_is_minimized(void) { return IsIconic(ui_app_window()); }\n\nstatic bool ui_app_is_maximized(void) { return IsZoomed(ui_app_window()); }\n\nstatic bool ui_app_focused(void) { return GetFocus() == ui_app_window(); }\n\nstatic void window_request_focus(void* w) {\n    // https://stackoverflow.com/questions/62649124/pywin32-setfocus-resulting-in-access-is-denied-error\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput\n    rt_assert(rt_thread.id() == ui_app.tid, \"cannot be called from background thread\");\n    rt_core.set_err(0);\n    HWND previous = SetFocus((HWND)w); // previously focused window\n    if (previous == null) { rt_fatal_if_error(rt_core.err()); }\n}\n\nstatic void ui_app_request_focus(void) {\n    window_request_focus(ui_app.window);\n}\n\nstatic void ui_app_init(void) {\n    ui_app_event_quit           = rt_event.create_manual();\n    ui_app_event_invalidate     = rt_event.create();\n    ui_app.request_redraw       = ui_app_request_redraw;\n    ui_app.post                 = ui_app_post;\n    ui_app.draw                 = ui_app_draw;\n    ui_app.px2in                = ui_app_px2in;\n    ui_app.in2px                = ui_app_in2px;\n    ui_app.set_layered_window   = ui_app_set_layered_window;\n    ui_app.is_active            = ui_app_is_active;\n    ui_app.is_minimized         = ui_app_is_minimized;\n    ui_app.is_maximized         = ui_app_is_maximized;\n    ui_app.focused              = ui_app_focused;\n    ui_app.request_focus        = ui_app_request_focus;\n    ui_app.activate             = ui_app_activate;\n    ui_app.set_title            = ui_app_set_title;\n    ui_app.capture_mouse        = ui_app_capture_mouse;\n    ui_app.move_and_resize      = ui_app_move_and_resize;\n    ui_app.bring_to_foreground  = ui_app_bring_to_foreground;\n    ui_app.make_topmost         = ui_app_make_topmost;\n    ui_app.bring_to_front       = ui_app_bring_to_front;\n    ui_app.request_layout       = ui_app_request_layout;\n    ui_app.invalidate           = ui_app_invalidate_rect;\n    ui_app.full_screen          = ui_app_full_screen;\n    ui_app.set_cursor           = ui_app_cursor_set;\n    ui_app.close                = ui_app_close_window;\n    ui_app.quit                 = ui_app_quit;\n    ui_app.set_timer            = ui_app_timer_set;\n    ui_app.kill_timer           = ui_app_timer_kill;\n    ui_app.show_window          = ui_app_show_window;\n    ui_app.show_toast           = ui_app_show_toast;\n    ui_app.show_hint            = ui_app_show_hint;\n    ui_app.toast_va             = ui_app_formatted_toast_va;\n    ui_app.toast                = ui_app_formatted_toast;\n    ui_app.create_caret         = ui_app_create_caret;\n    ui_app.show_caret           = ui_app_show_caret;\n    ui_app.move_caret           = ui_app_move_caret;\n    ui_app.hide_caret           = ui_app_hide_caret;\n    ui_app.destroy_caret        = ui_app_destroy_caret;\n    ui_app.beep                 = ui_app_beep;\n    ui_app.data_save            = ui_app_data_save;\n    ui_app.data_size            = ui_app_data_size;\n    ui_app.data_load            = ui_app_data_load;\n    ui_app.open_file            = ui_app_open_file;\n    ui_app.is_stdout_redirected = ui_app_is_stdout_redirected;\n    ui_app.is_console_visible   = ui_app_is_console_visible;\n    ui_app.console_attach       = ui_app_console_attach;\n    ui_app.console_create       = ui_app_console_create;\n    ui_app.console_show         = ui_app_console_show;\n    ui_app.root    = &ui_app_view;\n    ui_app.content = &ui_app_content;\n    ui_app.caption = &ui_caption.view;\n    ui_app.root->hit_test = ui_app_root_hit_test;\n    ui_view.add(ui_app.root, ui_app.caption, ui_app.content, null);\n    ui_view_call_init(ui_app.root); // to get done with container_init()\n    rt_assert(ui_app.content->type == ui_view_stack);\n    rt_assert(ui_app.content->background == ui_colors.transparent);\n    ui_app.root->color_id = ui_color_id_window_text;\n    ui_app.root->background_id = ui_color_id_window;\n    ui_app.root->insets  = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.root->padding = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.root->paint = ui_app_view_paint;\n    ui_app.root->max_w = ui.infinity;\n    ui_app.root->max_h = ui.infinity;\n    ui_app.content->insets  = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.content->padding = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.content->max_w = ui.infinity;\n    ui_app.content->max_h = ui.infinity;\n    ui_app.caption->state.hidden = !ui_app.no_decor;\n    // for ui_view_debug_paint:\n    ui_view.set_text(ui_app.root, \"ui_app.root\");\n    ui_view.set_text(ui_app.content, \"ui_app.content\");\n    if (ui_app.init != null) { ui_app.init(); }\n}\n\nstatic void ui_app_set_dpi_awareness(void) {\n    // Mutually exclusive:\n    // BOOL SetProcessDpiAwarenessContext()\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext\n    // and\n    // HRESULT SetProcessDpiAwareness()\n    // https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness\n    // Plus DPI awareness can be set by APP .exe shell properties, registry\n    // or Windows policy. See:\n    // https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/\n    DPI_AWARENESS_CONTEXT dpi_awareness_context_1 =\n        GetThreadDpiAwarenessContext();\n    // https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/\n    errno_t error = rt_b2e(SetProcessDpiAwarenessContext(\n            DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2));\n    if (error == ERROR_ACCESS_DENIED) {\n        rt_println(\"Warning: SetProcessDpiAwarenessContext(): ERROR_ACCESS_DENIED\");\n        // dpi awareness already set, manifest, registry, windows policy\n        // Try via Shell:\n        HRESULT hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);\n        if (hr == E_ACCESSDENIED) {\n            rt_println(\"Warning: SetProcessDpiAwareness(): E_ACCESSDENIED\");\n        }\n    }\n    DPI_AWARENESS_CONTEXT dpi_awareness_context_2 =\n        GetThreadDpiAwarenessContext();\n    rt_swear(dpi_awareness_context_1 != dpi_awareness_context_2);\n}\n\nstatic void ui_app_init_windows(void) {\n    ui_app_set_dpi_awareness();\n    InitCommonControls(); // otherwise GetOpenFileName does not work\n    ui_app.dpi.process = (int32_t)GetSystemDpiForProcess(GetCurrentProcess());\n    ui_app.dpi.system = (int32_t)GetDpiForSystem(); // default was 96DPI\n    // monitor dpi will be reinitialized in load_window_pos\n    ui_app.dpi.monitor_effective = ui_app.dpi.system;\n    ui_app.dpi.monitor_angular = ui_app.dpi.system;\n    ui_app.dpi.monitor_raw = ui_app.dpi.system;\n    ui_app.dpi.monitor_max = ui_app.dpi.system;\n//  rt_println(\"ui_app.dpi.monitor_max := %d\", ui_app.dpi.system);\n    static const RECT nowhere = {0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF};\n    ui_rect_t r = ui_app_rect2ui(&nowhere);\n    ui_app_update_mi(&r, MONITOR_DEFAULTTOPRIMARY);\n    ui_app.dpi.window = ui_app.dpi.monitor_effective;\n}\n\nstatic ui_rect_t ui_app_window_initial_rectangle(void) {\n    const ui_window_sizing_t* ws = &ui_app.window_sizing;\n    // it is not practical and thus not implemented handling\n    // == (0, 0) and != (0, 0) for sizing half dimension (only w or only h)\n    rt_swear((ws->min_w != 0) == (ws->min_h != 0) &&\n           ws->min_w >= 0 && ws->min_h >= 0,\n          \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f\", ws->min_w, ws->min_h);\n    rt_swear((ws->ini_w != 0) == (ws->ini_h != 0) &&\n           ws->ini_w >= 0 && ws->ini_h >= 0,\n          \"ui_app.window_sizing .ini_w=%.1f .ini_h=%.1f\", ws->ini_w, ws->ini_h);\n    rt_swear((ws->max_w != 0) == (ws->max_h != 0) &&\n           ws->max_w >= 0 && ws->max_h >= 0,\n          \"ui_app.window_sizing .max_w=%.1f .max_h=%.1f\", ws->max_w, ws->max_h);\n    // if max is set then min and ini must be less than max\n    if (ws->max_w != 0 || ws->max_h != 0) {\n        rt_swear(ws->min_w <= ws->max_w && ws->min_h <= ws->max_h,\n            \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f .max_w=%1.f .max_h=%.1f\",\n             ws->min_w, ws->min_h, ws->max_w, ws->max_h);\n        rt_swear(ws->ini_w <= ws->max_w && ws->ini_h <= ws->max_h,\n            \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f .max_w=%1.f .max_h=%.1f\",\n                ws->ini_w, ws->ini_h, ws->max_w, ws->max_h);\n    }\n    const int32_t ini_w = ui_app.in2px(ws->ini_w);\n    const int32_t ini_h = ui_app.in2px(ws->ini_h);\n    int32_t min_w = ws->min_w > 0 ? ui_app.in2px(ws->min_w) : ui_app.work_area.w / 4;\n    int32_t min_h = ws->min_h > 0 ? ui_app.in2px(ws->min_h) : ui_app.work_area.h / 4;\n    // (x, y) (-1, -1) means \"let Windows manager position the window\"\n    ui_rect_t r = {-1, -1,\n                   ini_w > 0 ? ini_w : min_w, ini_h > 0 ? ini_h : min_h};\n    return r;\n}\n\nstatic FILE* ui_app_crash_log;\n\nstatic bool ui_app_write_backtrace(const char* s, int32_t n) {\n    if (n > 0 && s[n - 1] == 0) { n--; }\n    if (n > 0 && ui_app_crash_log != null) {\n        fwrite(s, n, 1, ui_app_crash_log);\n    }\n    return false;\n}\n\nstatic LONG ui_app_exception_filter(EXCEPTION_POINTERS* ep) {\n    char fn[1024];\n    DWORD ex = ep->ExceptionRecord->ExceptionCode; // exception code\n    // T-connector for intercepting rt_debug.output:\n    bool (*tee)(const char* s, int32_t n) = rt_debug.tee;\n    rt_debug.tee = ui_app_write_backtrace;\n    const char* home = rt_files.known_folder(rt_files.folder.home);\n    if (home != null) {\n        const char* name = ui_app.class_name  != null ?\n                           ui_app.class_name : \"ui_app\";\n        rt_str_printf(fn, \"%s\\\\%s_crash_log.txt\", home, name);\n        ui_app_crash_log = fopen(fn, \"w\");\n    }\n    rt_debug.println(null, 0, null,\n        \"To file and issue report copy this log and\");\n    rt_debug.println(null, 0, null,\n        \"paste it here: https://github.com/leok7v/ui/discussions/4\");\n    rt_debug.println(null, 0, null,\n        \"%s exception: %s\", rt_args.basename(), rt_str.error(ex));\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.context(rt_thread.self(), ep->ContextRecord, &bt);\n    rt_backtrace.trace(&bt, \"*\");\n    rt_backtrace.trace_all_but_self();\n    rt_debug.tee = tee;\n    if (ui_app_crash_log != null) {\n        fclose(ui_app_crash_log);\n        char cmd[1024];\n        rt_str_printf(cmd, \"cmd.exe /c start notepad \\\"%s\\\"\", fn);\n        system(cmd);\n    }\n    return EXCEPTION_CONTINUE_SEARCH;\n}\n\n#undef UI_APP_TEST_POST\n\n#ifdef UI_APP_TEST_POST\n\n// The dispatch_until() is just for testing purposes.\n// Usually rt_work_queue.dispatch(q) will be called inside each\n// iteration of message loop of a dispatch [UI] thread.\n\nstatic void ui_app_test_dispatch_until(rt_work_queue_t* q, int32_t* i,\n        const int32_t n) {\n    while (q->head != null && *i < n) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(q);\n    }\n    rt_work_queue.flush(q);\n}\n\n// simple way of passing a single pointer to call_later\n\nstatic void ui_app_test_every_100ms(rt_work_t* w) {\n    int32_t* i = (int32_t*)w->data;\n    rt_println(\"i: %d\", *i);\n    (*i)++;\n    w->when = rt_clock.seconds() + 0.100;\n    rt_work_queue.post(w);\n}\n\nstatic void ui_app_test_work_queue_1(void) {\n    rt_work_queue_t queue = {0};\n    // if a single pointer will suffice\n    int32_t i = 0;\n    rt_work_t work = {\n        .queue = &queue,\n        .when  = rt_clock.seconds() + 0.100,\n        .work  = ui_app_test_every_100ms,\n        .data  = &i\n    };\n    rt_work_queue.post(&work);\n    ui_app_test_dispatch_until(&queue, &i, 4);\n}\n\n// extending rt_work_t with extra data:\n\ntypedef struct rt_work_ex_s {\n    union {\n        rt_work_t base;\n        struct rt_work_s;\n    };\n    struct { int32_t a; int32_t b; } s;\n    int32_t i;\n} rt_work_ex_t;\n\nstatic void ui_app_test_every_200ms(rt_work_t* w) {\n    rt_work_ex_t* ex = (rt_work_ex_t*)w;\n    rt_println(\"ex { .i: %d, .s.a: %d .s.b: %d}\", ex->i, ex->s.a, ex->s.b);\n    ex->i++;\n    const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n    w->when = rt_clock.seconds() + 0.200;\n    rt_work_queue.post(w);\n}\n\nstatic void ui_app_test_work_queue_2(void) {\n    rt_work_queue_t queue = {0};\n    rt_work_ex_t work = {\n        .queue = &queue,\n        .when  = rt_clock.seconds() + 0.200,\n        .work  = ui_app_test_every_200ms,\n        .data  = null,\n        .s = { .a = 1, .b = 2 },\n        .i = 0\n    };\n    rt_work_queue.post(&work.base);\n    ui_app_test_dispatch_until(&queue, &work.i, 4);\n}\n\nstatic fp64_t ui_app_test_timestamp_0;\nstatic fp64_t ui_app_test_timestamp_2;\nstatic fp64_t ui_app_test_timestamp_3;\nstatic fp64_t ui_app_test_timestamp_4;\n\nstatic void ui_app_test_in_1_second(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_3 = rt_clock.seconds();\n    rt_println(\"ETA 3 seconds\");\n}\n\nstatic void ui_app_test_in_2_seconds(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_2 = rt_clock.seconds();\n    rt_println(\"ETA 2 seconds\");\n    static rt_work_t invoke_in_1_seconds;\n    invoke_in_1_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 1.0, // seconds\n        .work = ui_app_test_in_1_second\n    };\n    ui_app.post(&invoke_in_1_seconds);\n}\n\nstatic void ui_app_test_in_4_seconds(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_4 = rt_clock.seconds();\n    rt_println(\"ETA 4 seconds\");\n//  expected sequence of callbacks:\n//  2:732 ui_app_test_in_2_seconds ETA 2 seconds\n//  3:724 ui_app_test_in_1_second  ETA 3 seconds\n//  4:735 ui_app_test_in_4_seconds ETA 4 seconds\n    fp64_t dt2 = ui_app_test_timestamp_2 - ui_app_test_timestamp_0;\n    fp64_t dt3 = ui_app_test_timestamp_3 - ui_app_test_timestamp_0;\n    fp64_t dt4 = ui_app_test_timestamp_4 - ui_app_test_timestamp_0;\n//  Assuming there were no huge startup delays:\n    swear(1.75 < dt2 < 2.25);\n    swear(2.75 < dt3 < 3.25);\n    swear(3.75 < dt4 < 4.25);\n}\n\nstatic void ui_app_test_post(void) {\n    ui_app_test_work_queue_1();\n    ui_app_test_work_queue_2();\n    rt_println(\"see Output/Timestamps\");\n    static rt_work_t invoke_in_2_seconds;\n    static rt_work_t invoke_in_4_seconds;\n    ui_app_test_timestamp_0 = rt_clock.seconds();\n    invoke_in_2_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 2.0, // seconds\n        .work = ui_app_test_in_2_seconds\n    };\n    invoke_in_4_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 4.0, // seconds\n        .work = ui_app_test_in_4_seconds\n    };\n    ui_app.post(&invoke_in_4_seconds);\n    ui_app.post(&invoke_in_2_seconds);\n}\n\n#endif\n\nstatic int ui_app_win_main(HINSTANCE instance) {\n    // IDI_ICON 101:\n    ui_app.icon = (ui_icon_t)LoadIconW(instance, MAKEINTRESOURCE(101));\n    ui_app_init_windows();\n    ui_gdi.init();\n    rt_clipboard.put_image = ui_app_clipboard_put_image;\n    ui_app.last_visibility = ui.visibility.defau1t;\n    ui_app_init();\n    int r = 0;\n//  ui_app_dump_dpi();\n    // It is possible (but not trivial) to ask DWM to create taller tittle bar:\n    // https://learn.microsoft.com/en-us/windows/win32/dwm/customframe\n    // TODO: if any app need to make to app store they will probably ask for it\n    // \"wr\" Window Rect in pixels: default is -1,-1, ini_w, ini_h\n    ui_rect_t wr = ui_app_window_initial_rectangle();\n    ui_app.caption_height = (int32_t)GetSystemMetricsForDpi(SM_CYCAPTION,\n                                (uint32_t)ui_app.dpi.process);\n    ui_app.border.w = (int32_t)GetSystemMetricsForDpi(SM_CXSIZEFRAME,\n                                (uint32_t)ui_app.dpi.process);\n    ui_app.border.h = (int32_t)GetSystemMetricsForDpi(SM_CYSIZEFRAME,\n                                (uint32_t)ui_app.dpi.process);\n\n    if (ui_app.no_decor) {\n        // border is too think (5 pixels) narrow down to 3x3\n        const int32_t max_border = ui_app.dpi.window <= 100 ? 1 :\n            (ui_app.dpi.window >= 192 ? 3 : 2);\n        ui_app.border.w = rt_min(max_border, ui_app.border.w);\n        ui_app.border.h = rt_min(max_border, ui_app.border.h);\n    }\n//  rt_println(\"frame: %d,%d caption_height: %d\", ui_app.border.w, ui_app.border.h, ui_app.caption_height);\n    // TODO: use AdjustWindowRectEx instead\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-adjustwindowrectex\n    wr.x -= ui_app.border.w;\n    wr.w += ui_app.border.w * 2;\n    wr.y -= ui_app.border.h + ui_app.caption_height;\n    wr.h += ui_app.border.h * 2 + ui_app.caption_height;\n    if (!ui_app_load_window_pos(&wr, &ui_app.last_visibility)) {\n        // first time - center window\n        wr.x = ui_app.work_area.x + (ui_app.work_area.w - wr.w) / 2;\n        wr.y = ui_app.work_area.y + (ui_app.work_area.h - wr.h) / 2;\n        ui_app_bring_window_inside_monitor(&ui_app.mrc, &wr);\n    }\n    ui_app.root->state.hidden = true; // start with ui hidden\n    ui_app.root->fm = &ui_app.fm.prop.normal;\n    ui_app.root->w = wr.w - ui_app.border.w * 2;\n    ui_app.root->h = wr.h - ui_app.border.h * 2 - ui_app.caption_height;\n    ui_app_layout_dirty = true; // layout will be done before first paint\n    rt_not_null(ui_app.class_name);\n    ui_app_wt = (rt_event_t)CreateWaitableTimerA(null, false, null);\n    rt_thread_t alarm  = rt_thread.start(ui_app_alarm_thread, null);\n    if (!ui_app.no_ui) {\n        ui_app_create_window(wr);\n        ui_app_init_fonts(ui_app.dpi.window);\n        rt_thread_t redraw = rt_thread.start(ui_app_redraw_thread, null);\n        #ifdef UI_APP_TEST_POST\n            ui_app_test_post();\n        #endif\n        r = ui_app_message_loop();\n        // ui_app.fini() must be called before ui_app_dispose()\n        if (ui_app.fini != null) { ui_app.fini(); }\n        rt_event.set(ui_app_event_quit);\n        rt_thread.join(redraw, -1);\n        ui_app_dispose();\n        if (r == 0 && ui_app.exit_code != 0) { r = ui_app.exit_code; }\n    } else {\n        r = ui_app.main();\n        if (ui_app.fini != null) { ui_app.fini(); }\n    }\n    rt_event.set(ui_app_event_quit);\n    rt_thread.join(alarm, -1);\n    rt_event.dispose(ui_app_event_quit);\n    ui_app_event_quit = null;\n    rt_event.dispose(ui_app_wt);\n    ui_app_wt = null;\n    ui_gdi.fini();\n    return r;\n}\n\n#pragma warning(disable: 28251) // inconsistent annotations\n\nint WINAPI WinMain(HINSTANCE instance, HINSTANCE rt_unused(previous),\n        char* rt_unused(command), int show) {\n    SetUnhandledExceptionFilter(ui_app_exception_filter);\n    const COINIT co_init = COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY;\n    rt_fatal_if_error(CoInitializeEx(0, co_init));\n    SetConsoleCP(CP_UTF8);\n    // Expected manifest.xml containing UTF-8 code page\n    // for TranslateMessage and WM_CHAR to deliver UTF-8 characters\n    // see:\n    // https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page\n    // .rc file must have:\n    // 1 RT_MANIFEST \"manifest.xml\"\n    if (GetACP() != 65001) {\n        rt_println(\"codepage: %d UTF-8 will not be supported\", GetACP());\n    }\n    // at the moment of writing there is no API call to inform Windows about process\n    // preferred codepage except manifest.xml file in resource #1.\n    // Absence of manifest.xml will result to ancient and useless ANSI 1252 codepage\n    // TODO: may need to change CreateWindowA() to CreateWindowW() and\n    // translate UTF16 to UTF8\n    ui_app.tid = rt_thread.id();\n    rt_nls.init();\n    ui_app.visibility = show;\n    rt_args.WinMain();\n    int32_t r = ui_app_win_main(instance);\n    rt_args.fini();\n    return r;\n}\n\nint main(int argc, const char* argv[], const char** envp) {\n    SetUnhandledExceptionFilter(ui_app_exception_filter);\n    rt_fatal_if_error(CoInitializeEx(0, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY));\n    rt_args.main(argc, argv, envp);\n    rt_nls.init();\n    ui_app.tid = rt_thread.id();\n    int r = ui_app.main();\n    rt_args.fini();\n    return r;\n}\n\n#pragma pop_macro(\"ui_app_canvas\")\n#pragma pop_macro(\"ui_app_window\")\n\n#pragma comment(lib, \"comctl32\")\n#pragma comment(lib, \"comdlg32\")\n#pragma comment(lib, \"dwmapi\")\n#pragma comment(lib, \"gdi32\")\n#pragma comment(lib, \"msimg32\")\n#pragma comment(lib, \"shcore\")\n#pragma comment(lib, \"uxtheme\")\n// _______________________________ ui_button.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic void ui_button_every_100ms(ui_view_t* v) { // every 100ms\n    if (!v->state.hidden) {\n        v->p.armed_until = 0;\n        v->state.armed = false;\n    } else if (v->p.armed_until != 0 && ui_app.now > v->p.armed_until) {\n        v->p.armed_until = 0;\n        v->state.armed = false;\n        ui_view.invalidate(v, null);\n    }\n    if (v->p.armed_until != 0) { ui_app.show_hint(null, -1, -1, 0); }\n}\n\nstatic void ui_button_paint(ui_view_t* v) {\n    bool pressed = (v->state.armed ^ v->state.pressed) == 0;\n    if (v->p.armed_until != 0) { pressed = true; }\n    const int32_t w = v->w;\n    const int32_t h = v->h;\n    const int32_t x = v->x;\n    const int32_t y = v->y;\n    const int32_t r = (0x1 | rt_max(3, v->fm->em.h / 4));  // odd radius\n    const fp32_t d = ui_theme.is_app_dark() ? 0.50f : 0.25f;\n    ui_color_t d0 = ui_colors.darken(v->background, d);\n    const fp32_t d2 = d / 2;\n    if (v->flat) {\n        if (v->state.hover) {\n            ui_color_t d1 = ui_theme.is_app_dark() ?\n                    ui_colors.lighten(v->background, d2) :\n                    ui_colors.darken(v->background,  d2);\n            if (!pressed) {\n                ui_gdi.gradient(x, y, w, h, d0, d1, true);\n            } else {\n                ui_gdi.gradient(x, y, w, h, d1, d0, true);\n            }\n        }\n    } else {\n        // `bc` border color\n        ui_color_t bc = ui_colors.get_color(ui_color_id_gray_text);\n        if (v->state.armed) { bc = ui_colors.lighten(bc, 0.125f); }\n        if (ui_view.is_disabled(v)) { bc = ui_color_rgb(30, 30, 30); } // TODO: hardcoded\n        if (v->state.hover && !v->state.armed) {\n            bc = ui_colors.get_color(ui_color_id_hot_tracking);\n        }\n        ui_color_t d1 = ui_colors.darken(v->background, d2);\n        ui_color_t fc = ui_colors.interpolate(d0, d1, 0.5f); // fill color\n        if (v->state.armed) {\n            fc = ui_colors.lighten(fc, 0.250f);\n        } else if (v->state.hover) {\n            fc = ui_colors.darken(fc, 0.250f);\n        }\n        ui_gdi.rounded(v->x, v->y, v->w, v->h, r, bc, fc);\n    }\n    const int32_t tx = v->x + v->text.xy.x;\n    const int32_t ty = v->y + v->text.xy.y;\n    if (v->icon == null) {\n        ui_color_t c = v->color;\n        if (v->state.hover && !v->state.armed) {\n            c = ui_theme.is_app_dark() ? ui_color_rgb(0xFF, 0xE0, 0xE0) :\n                                         ui_color_rgb(0x00, 0x40, 0xFF);\n        }\n        if (ui_view.is_disabled(v)) { c = ui_colors.get_color(ui_color_id_gray_text); }\n        if (v->debug.paint.fm) {\n            ui_view.debug_paint_fm(v);\n        }\n        const ui_gdi_ta_t ta = { .fm = v->fm, .color = c };\n        ui_gdi.text(&ta, tx, ty, \"%s\", ui_view.string(v));\n    } else {\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        const ui_wh_t i_wh = { .w = v->w - i.left - i.right,\n                               .h = v->h - i.top - i.bottom };\n        // TODO: icon text alignment\n        ui_gdi.icon(tx, ty + v->text.xy.y, i_wh.w, i_wh.h, v->icon);\n    }\n}\n\nstatic void ui_button_callback(ui_button_t* b) {\n    // for flip buttons the state of the button flips\n    // *before* callback.\n    if (b->flip) { b->state.pressed = !b->state.pressed; }\n    const bool pressed = b->state.pressed;\n    if (b->callback != null) { b->callback(b); }\n    if (pressed != b->state.pressed) {\n        if (b->flip) { // warn the client of strange logic:\n            rt_println(\"strange flip the button with button.flip: true\");\n            // if client wants to flip pressed state manually it\n            // should do it for the button.flip = false\n        }\n//      rt_println(\"disarmed immediately\");\n        b->p.armed_until = 0;\n        b->state.armed = false;\n    } else {\n        if (b->flip) {\n//          rt_println(\"disarmed immediately\");\n            b->p.armed_until = 0;\n            b->state.armed = false;\n        } else {\n//          rt_println(\"will disarm in 1/4 seconds\");\n            b->p.armed_until = ui_app.now + 0.250;\n        }\n    }\n}\n\nstatic void ui_button_trigger(ui_view_t* v) {\n    ui_button_t* b = (ui_button_t*)v;\n    v->state.armed = true;\n    ui_view.invalidate(v, null);\n    ui_button_callback(b);\n}\n\nstatic void ui_button_character(ui_view_t* v, const char* utf8) {\n    char ch = utf8[0]; // TODO: multibyte utf8 shortcuts?\n    if (ui_view.is_shortcut_key(v, ch)) {\n        ui_button_trigger(v);\n    }\n}\n\nstatic bool ui_button_key_pressed(ui_view_t* v, int64_t key) {\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    const bool trigger = ui_app.alt && ui_view.is_shortcut_key(v, key);\n    if (trigger) { ui_button_trigger(v); }\n    return trigger; // swallow if true\n}\n\nstatic bool ui_button_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    // 'ix' ignored - button index acts on any mouse button\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        ui_view.invalidate(v, null); // always on any press/release inside\n        ui_button_t* b = (ui_button_t*)v;\n        if (pressed && b->flip) {\n            if (b->flip) { ui_button_callback(b); }\n        } else if (pressed) {\n            v->state.armed = true;\n        } else { // released\n            if (!b->flip) { ui_button_callback(b); }\n        }\n    }\n    return pressed && inside; // swallow clicks inside\n}\n\nvoid ui_view_init_button(ui_view_t* v) {\n    rt_assert(v->type == ui_view_button);\n    v->tap           = ui_button_tap;\n    v->paint         = ui_button_paint;\n    v->character     = ui_button_character;\n    v->every_100ms   = ui_button_every_100ms;\n    v->key_pressed   = ui_button_key_pressed;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    if (v->debug.id == null) { v->debug.id = \"#button\"; }\n}\n\nvoid ui_button_init(ui_button_t* b, const char* label, fp32_t ems,\n        void (*callback)(ui_button_t* b)) {\n    b->type = ui_view_button;\n    ui_view.set_text(b, \"%s\", label);\n    b->callback = callback;\n    b->min_w_em = ems;\n    ui_view_init_button(b);\n}\n// _______________________________ ui_caption.c _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n\n#pragma push_macro(\"ui_caption_glyph_rest\")\n#pragma push_macro(\"ui_caption_glyph_menu\")\n#pragma push_macro(\"ui_caption_glyph_dark\")\n#pragma push_macro(\"ui_caption_glyph_light\")\n#pragma push_macro(\"ui_caption_glyph_mini\")\n#pragma push_macro(\"ui_caption_glyph_maxi\")\n#pragma push_macro(\"ui_caption_glyph_full\")\n#pragma push_macro(\"ui_caption_glyph_quit\")\n\n#define ui_caption_glyph_rest  rt_glyph_white_square_with_upper_right_quadrant // instead of rt_glyph_desktop_window\n#define ui_caption_glyph_menu  rt_glyph_trigram_for_heaven\n#define ui_caption_glyph_dark  rt_glyph_crescent_moon\n#define ui_caption_glyph_light rt_glyph_white_sun_with_rays\n#define ui_caption_glyph_mini  rt_glyph_minimize\n#define ui_caption_glyph_maxi  rt_glyph_white_square_with_lower_left_quadrant // instead of rt_glyph_maximize\n#define ui_caption_glyph_full  rt_glyph_square_four_corners\n#define ui_caption_glyph_quit  rt_glyph_cancellation_x\n\nstatic void ui_caption_toggle_full(void) {\n    ui_app.full_screen(!ui_app.is_full_screen);\n    ui_caption.view.state.hidden = ui_app.is_full_screen;\n    ui_app.request_layout();\n}\n\nstatic void ui_caption_esc_full_screen(ui_view_t* v, const char utf8[]) {\n    rt_swear(v == ui_caption.view.parent);\n    // TODO: inside ui_app.c instead of here?\n    if (utf8[0] == 033 && ui_app.is_full_screen) { ui_caption_toggle_full(); }\n}\n\nstatic void ui_caption_quit(ui_button_t* rt_unused(b)) {\n    ui_app.close();\n}\n\nstatic void ui_caption_mini(ui_button_t* rt_unused(b)) {\n    ui_app.show_window(ui.visibility.minimize);\n}\n\nstatic void ui_caption_mode_appearance(void) {\n    if (ui_theme.is_app_dark()) {\n        ui_view.set_text(&ui_caption.mode, \"%s\", ui_caption_glyph_light);\n        rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Light Mode\"));\n    } else {\n        ui_view.set_text(&ui_caption.mode, \"%s\", ui_caption_glyph_dark);\n        rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Dark Mode\"));\n    }\n}\n\nstatic void ui_caption_mode(ui_button_t* rt_unused(b)) {\n    bool was_dark = ui_theme.is_app_dark();\n    ui_app.light_mode =  was_dark;\n    ui_app.dark_mode  = !was_dark;\n    ui_theme.refresh();\n    ui_caption_mode_appearance();\n}\n\nstatic void ui_caption_maximize_or_restore(void) {\n    ui_view.set_text(&ui_caption.maxi, \"%s\",\n        ui_app.is_maximized() ?\n        ui_caption_glyph_rest : ui_caption_glyph_maxi);\n    rt_str_printf(ui_caption.maxi.hint, \"%s\",\n        ui_app.is_maximized() ?\n        rt_nls.str(\"Restore\") : rt_nls.str(\"Maximize\"));\n    // non-decorated windows on Win32 are \"popup\" style\n    // that cannot be maximized. Full screen will serve\n    // the purpose of maximization.\n    ui_caption.maxi.state.hidden = ui_app.no_decor;\n}\n\nstatic void ui_caption_maxi(ui_button_t* rt_unused(b)) {\n    if (!ui_app.is_maximized()) {\n        ui_app.show_window(ui.visibility.maximize);\n    } else if (ui_app.is_maximized() || ui_app.is_minimized()) {\n        ui_app.show_window(ui.visibility.restore);\n    }\n    ui_caption_maximize_or_restore();\n}\n\nstatic void ui_caption_full(ui_button_t* rt_unused(b)) {\n    ui_caption_toggle_full();\n}\n\nstatic int64_t ui_caption_hit_test(const ui_view_t* v, ui_point_t pt) {\n    rt_swear(v == &ui_caption.view);\n    rt_assert(ui_view.inside(v, &pt));\n//  rt_println(\"%d,%d ui_caption.icon: %d,%d %dx%d inside: %d\",\n//      x, y,\n//      ui_caption.icon.x, ui_caption.icon.y,\n//      ui_caption.icon.w, ui_caption.icon.h,\n//      ui_view.inside(&ui_caption.icon, &pt));\n    if (ui_app.is_full_screen) {\n        return ui.hit_test.client;\n    } else if (!ui_caption.icon.state.hidden &&\n                ui_view.inside(&ui_caption.icon, &pt)) {\n        return ui.hit_test.system_menu;\n    } else {\n        ui_view_for_each(&ui_caption.view, c, {\n            bool ignore = c->type == ui_view_stack ||\n                          c->type == ui_view_spacer ||\n                          c->type == ui_view_label;\n            if (!ignore && ui_view.inside(c, &pt)) {\n                return ui.hit_test.client;\n            }\n        });\n        return ui.hit_test.caption;\n    }\n}\n\nstatic ui_color_t ui_caption_color(void) {\n    ui_color_t c = ui_app.is_active() ?\n        ui_colors.get_color(ui_color_id_active_title) :\n        ui_colors.get_color(ui_color_id_inactive_title);\n    return c;\n}\n\nstatic const ui_margins_t ui_caption_button_button_padding =\n    { .left  = 0.25,  .top    = 0.0,\n      .right = 0.25,  .bottom = 0.0};\n\nstatic void ui_caption_button_measure(ui_view_t* v) {\n    rt_assert(v->type == ui_view_button);\n    ui_view.measure_control(v);\n    const int32_t dx = ui_app.caption_height - v->w;\n    const int32_t dy = ui_app.caption_height - v->h;\n    v->w += dx;\n    v->h += dy;\n    v->text.xy.x += dx / 2;\n    v->text.xy.y += dy / 2;\n    v->padding = ui_caption_button_button_padding;\n}\n\nstatic void ui_caption_button_icon_paint(ui_view_t* v) {\n    int32_t w = v->w;\n    int32_t h = v->h;\n    while (h > 16 && (h & (h - 1)) != 0) { h--; }\n    w = h;\n    int32_t dx = (v->w - w) / 2;\n    int32_t dy = (v->h - h) / 2;\n    ui_gdi.icon(v->x + dx, v->y + dy, w, h, v->icon);\n}\n\nstatic void ui_caption_prepare(ui_view_t* rt_unused(v)) {\n    ui_caption.title.state.hidden = false;\n}\n\nstatic void ui_caption_measured(ui_view_t* v) {\n    // remeasure all child buttons with hard override:\n    int32_t w = 0;\n    ui_view_for_each(v, it, {\n        if (it->type == ui_view_button) {\n            it->fm = &ui_app.fm.mono.normal;\n            it->flat = true;\n            ui_caption_button_measure(it);\n        }\n        if (!it->state.hidden) {\n            const ui_ltrb_t p = ui_view.margins(it, &it->padding);\n            w += it->w + p.left + p.right;\n        }\n    });\n    const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n    w += p.left + p.right;\n    // do not show title if there is not enough space\n    ui_caption.title.state.hidden = w > ui_app.root->w;\n    v->w = ui_app.root->w;\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    v->h = insets.top + ui_app.caption_height + insets.bottom;\n}\n\nstatic void ui_caption_composed(ui_view_t* v) {\n    v->x = ui_app.root->x;\n    v->y = ui_app.root->y;\n}\n\nstatic void ui_caption_paint(ui_view_t* v) {\n    ui_color_t background = ui_caption_color();\n    ui_gdi.fill(v->x, v->y, v->w, v->h, background);\n}\n\nstatic void ui_caption_init(ui_view_t* v) {\n    rt_swear(v == &ui_caption.view, \"caption is a singleton\");\n    ui_view_init_span(v);\n    ui_caption.view.insets = (ui_margins_t){ 0.125, 0.0, 0.125, 0.0 };\n    ui_caption.view.state.hidden = false;\n    v->parent->character = ui_caption_esc_full_screen; // ESC for full screen\n    ui_view.add(&ui_caption.view,\n        &ui_caption.icon,\n        &ui_caption.menu,\n        &ui_caption.title,\n        &ui_caption.spacer,\n        &ui_caption.mode,\n        &ui_caption.mini,\n        &ui_caption.maxi,\n        &ui_caption.full,\n        &ui_caption.quit,\n        null);\n    ui_caption.view.color_id = ui_color_id_window_text;\n    static const ui_margins_t p0 = { .left  = 0.0,   .top    = 0.0,\n                                     .right = 0.0,   .bottom = 0.0};\n    static const ui_margins_t pd = { .left  = 0.25,  .top    = 0.0,\n                                     .right = 0.25,  .bottom = 0.0};\n    static const ui_margins_t in = { .left  = 0.0,   .top    = 0.0,\n                                     .right = 0.0,   .bottom = 0.0};\n    ui_view_for_each(&ui_caption.view, c, {\n        c->fm = &ui_app.fm.prop.normal;\n        c->color_id = ui_caption.view.color_id;\n        if (c->type != ui_view_button) {\n            c->padding = pd;\n        }\n        c->insets  = in;\n        c->h = ui_app.caption_height;\n        c->min_w_em = 0.5f;\n        c->min_h_em = 0.5f;\n    });\n    rt_str_printf(ui_caption.menu.hint, \"%s\", rt_nls.str(\"Menu\"));\n    rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Light Mode\"));\n    rt_str_printf(ui_caption.mini.hint, \"%s\", rt_nls.str(\"Minimize\"));\n    rt_str_printf(ui_caption.maxi.hint, \"%s\", rt_nls.str(\"Maximize\"));\n    rt_str_printf(ui_caption.full.hint, \"%s\", rt_nls.str(\"Full Screen (ESC to restore)\"));\n    rt_str_printf(ui_caption.quit.hint, \"%s\", rt_nls.str(\"Close\"));\n    ui_caption.icon.icon     = ui_app.icon;\n    ui_caption.icon.padding  = p0;\n    ui_caption.icon.paint    = ui_caption_button_icon_paint;\n    ui_caption.view.align    = ui.align.left;\n    ui_caption.view.prepare  = ui_caption_prepare;\n    ui_caption.view.measured = ui_caption_measured;\n    ui_caption.view.composed = ui_caption_composed;\n    ui_view.set_text(&ui_caption.view, \"#ui_caption\"); // for debugging\n    ui_caption_maximize_or_restore();\n    ui_caption.view.paint = ui_caption_paint;\n    ui_caption_mode_appearance();\n    ui_caption.icon.debug.id = \"#caption.icon\";\n    ui_caption.menu.debug.id = \"#caption.menu\";\n    ui_caption.mode.debug.id = \"#caption.mode\";\n    ui_caption.mini.debug.id = \"#caption.mini\";\n    ui_caption.maxi.debug.id = \"#caption.maxi\";\n    ui_caption.full.debug.id = \"#caption.full\";\n    ui_caption.quit.debug.id = \"#caption.quit\";\n    ui_caption.title.debug.id  = \"#caption.title\";\n    ui_caption.spacer.debug.id = \"#caption.spacer\";\n\n}\n\nui_caption_t ui_caption =  {\n    .view = {\n        .type     = ui_view_span,\n        .fm       = &ui_app.fm.prop.normal,\n        .init     = ui_caption_init,\n        .hit_test = ui_caption_hit_test,\n        .state.hidden = true\n    },\n    .icon   = ui_button(rt_glyph_nbsp, 0.0, null),\n    .title  = ui_label(0, \"\"),\n    .spacer = ui_view(spacer),\n    .menu   = ui_button(ui_caption_glyph_menu, 0.0, null),\n    .mode   = ui_button(ui_caption_glyph_mini, 0.0, ui_caption_mode),\n    .mini   = ui_button(ui_caption_glyph_mini, 0.0, ui_caption_mini),\n    .maxi   = ui_button(ui_caption_glyph_maxi, 0.0, ui_caption_maxi),\n    .full   = ui_button(ui_caption_glyph_full, 0.0, ui_caption_full),\n    .quit   = ui_button(ui_caption_glyph_quit, 0.0, ui_caption_quit),\n};\n\n#pragma pop_macro(\"ui_caption_glyph_rest\")\n#pragma pop_macro(\"ui_caption_glyph_menu\")\n#pragma pop_macro(\"ui_caption_glyph_dark\")\n#pragma pop_macro(\"ui_caption_glyph_light\")\n#pragma pop_macro(\"ui_caption_glyph_mini\")\n#pragma pop_macro(\"ui_caption_glyph_maxi\")\n#pragma pop_macro(\"ui_caption_glyph_full\")\n#pragma pop_macro(\"ui_caption_glyph_quit\")\n// _______________________________ ui_colors.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic inline uint8_t ui_color_clamp_uint8(fp64_t value) {\n    return value < 0 ? 0 : (value > 255 ? 255 : (uint8_t)value);\n}\n\nstatic inline fp64_t ui_color_fp64_min(fp64_t x, fp64_t y) { return x < y ? x : y; }\n\nstatic inline fp64_t ui_color_fp64_max(fp64_t x, fp64_t y) { return x > y ? x : y; }\n\nstatic void ui_color_rgb_to_hsi(fp64_t r, fp64_t g, fp64_t b, fp64_t *h, fp64_t *s, fp64_t *i) {\n    r /= 255.0;\n    g /= 255.0;\n    b /= 255.0;\n    fp64_t min_val = ui_color_fp64_min(r, ui_color_fp64_min(g, b));\n    *i = (r + g + b) / 3;\n    fp64_t chroma = ui_color_fp64_max(r, ui_color_fp64_max(g, b)) - min_val;\n    if (chroma == 0) {\n        *h = 0;\n        *s = 0;\n    } else {\n        *s = 1 - min_val / *i;\n        if (*i > 0) { *s = chroma / (*i * 3); }\n        if (r == ui_color_fp64_max(r, ui_color_fp64_max(g, b))) {\n            *h = (g - b) / chroma + (g < b ? 6 : 0);\n        } else if (g == ui_color_fp64_max(r, ui_color_fp64_max(g, b))) {\n            *h = (b - r) / chroma + 2;\n        } else {\n            *h = (r - g) / chroma + 4;\n        }\n        *h *= 60;\n    }\n}\n\nstatic ui_color_t ui_color_hsi_to_rgb(fp64_t h, fp64_t s, fp64_t i, uint8_t a) {\n    h /= 60.0;\n    fp64_t f = h - (int32_t)h;\n    fp64_t p = i * (1 - s);\n    fp64_t q = i * (1 - s * f);\n    fp64_t t = i * (1 - s * (1 - f));\n    fp64_t r = 0, g = 0, b = 0;\n    switch ((int32_t)h) {\n        case 0:\n        case 6: r = i * 255; g = t * 255; b = p * 255; break;\n        case 1: r = q * 255; g = i * 255; b = p * 255; break;\n        case 2: r = p * 255; g = i * 255; b = t * 255; break;\n        case 3: r = p * 255; g = q * 255; b = i * 255; break;\n        case 4: r = t * 255; g = p * 255; b = i * 255; break;\n        case 5: r = i * 255; g = p * 255; b = q * 255; break;\n        default: rt_swear(false); break;\n    }\n    rt_assert(0 <= r && r <= 255);\n    rt_assert(0 <= g && g <= 255);\n    rt_assert(0 <= b && b <= 255);\n    return ui_color_rgba((uint8_t)r, (uint8_t)g, (uint8_t)b, a);\n}\n\nstatic ui_color_t ui_color_brightness(ui_color_t c, fp32_t multiplier) {\n    fp64_t h, s, i;\n    ui_color_rgb_to_hsi(ui_color_r(c), ui_color_g(c), ui_color_b(c), &h, &s, &i);\n    i = ui_color_fp64_max(0, ui_color_fp64_min(1, i * (fp64_t)multiplier));\n    return ui_color_hsi_to_rgb(h, s, i, ui_color_a(c));\n}\n\nstatic ui_color_t ui_color_saturation(ui_color_t c, fp32_t multiplier) {\n    fp64_t h, s, i;\n    ui_color_rgb_to_hsi(ui_color_r(c), ui_color_g(c), ui_color_b(c), &h, &s, &i);\n    s = ui_color_fp64_max(0, ui_color_fp64_min(1, s * (fp64_t)multiplier));\n    return ui_color_hsi_to_rgb(h, s, i, ui_color_a(c));\n}\n\n// Using the ui_color_interpolate function to blend colors toward\n// black or white can effectively adjust brightness and saturation,\n// offering more flexibility  and potentially better results in\n// terms of visual transitions between colors.\n\nstatic ui_color_t ui_color_interpolate(ui_color_t c0, ui_color_t c1,\n        fp32_t multiplier) {\n    rt_assert(0.0f < multiplier && multiplier < 1.0f);\n    fp64_t h0, s0, i0, h1, s1, i1;\n    ui_color_rgb_to_hsi(ui_color_r(c0), ui_color_g(c0), ui_color_b(c0),\n                       &h0, &s0, &i0);\n    ui_color_rgb_to_hsi(ui_color_r(c1), ui_color_g(c1), ui_color_b(c1),\n                       &h1, &s1, &i1);\n    fp64_t h = h0 + (h1 - h0) * (fp64_t)multiplier;\n    fp64_t s = s0 + (s1 - s0) * (fp64_t)multiplier;\n    fp64_t i = i0 + (i1 - i0) * (fp64_t)multiplier;\n    // Interpolate alphas only if differ\n    uint8_t a0 = ui_color_a(c0);\n    uint8_t a1 = ui_color_a(c1);\n    uint8_t a = a0 == a1 ? a0 : ui_color_clamp_uint8(a0 + (a1 - a0) * (fp64_t)multiplier);\n    return ui_color_hsi_to_rgb(h, s, i, a);\n}\n\n// Helper to get a neutral gray with the same intensity\n\nstatic ui_color_t ui_color_gray_with_same_intensity(ui_color_t c) {\n    uint8_t intensity = (ui_color_r(c) + ui_color_g(c) + ui_color_b(c)) / 3;\n    return ui_color_rgba(intensity, intensity, intensity, ui_color_a(c));\n}\n\n// Adjust brightness by interpolating towards black or white\n// using interpolation:\n//\n// To darken the color: Interpolate between\n// the color and black (rgba(0,0,0,255)).\n//\n// To lighten the color: Interpolate between\n// the color and white (rgba(255,255,255,255)).\n//\n// This approach allows you to manipulate the\n// brightness by specifying how close the color\n// should be to either black or white,\n// providing a smooth transition.\n\nstatic ui_color_t ui_color_adjust_brightness(ui_color_t c,\n        fp32_t multiplier, bool lighten) {\n    ui_color_t target = lighten ?\n        ui_color_rgba(255, 255, 255, ui_color_a(c)) :\n        ui_color_rgba(  0,   0,   0, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\n\nstatic ui_color_t ui_color_lighten(ui_color_t c, fp32_t multiplier) {\n    const ui_color_t target = ui_color_rgba(255, 255, 255, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\nstatic ui_color_t ui_color_darken(ui_color_t c, fp32_t multiplier) {\n    const ui_color_t target = ui_color_rgba(0, 0, 0, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\n\n// Adjust saturation by interpolating towards a gray of the same intensity\n//\n// To adjust saturation, the approach is similar but slightly\n// more nuanced because saturation involves both the color's\n// purity and its brightness:\n\nstatic ui_color_t ui_color_adjust_saturation(ui_color_t c,\n        fp32_t multiplier) {\n    ui_color_t gray = ui_color_gray_with_same_intensity(c);\n    return ui_color_interpolate(c, gray, 1 - multiplier);\n}\n\nstatic struct {\n    const char* name;\n    ui_color_t  dark;\n    ui_color_t  light;\n} ui_theme_colors[] = { // empirical\n    { .name = \"Undefiled\"        ,.dark = ui_color_undefined, .light = ui_color_undefined },\n    { .name = \"ActiveTitle\"      ,.dark = 0x001F1F1F, .light = 0x00D1B499 },\n    { .name = \"ButtonFace\"       ,.dark = 0x00333333, .light = 0x00F0F0F0 },\n    { .name = \"ButtonText\"       ,.dark = 0x00C8C8C8, .light = 0x00161616 },\n//  { .name = \"ButtonText\"       ,.dark = 0x00F6F3EE, .light = 0x00000000 },\n    { .name = \"GrayText\"         ,.dark = 0x00666666, .light = 0x006D6D6D },\n    { .name = \"Hilight\"          ,.dark = 0x00626262, .light = 0x00D77800 },\n    { .name = \"HilightText\"      ,.dark = 0x00000000, .light = 0x00FFFFFF },\n    { .name = \"HotTrackingColor\" ,.dark = 0x00B16300, .light = 0x00FF0000 }, // automatic Win11 \"accent\" ABRG: 0xFFB16300\n//  { .name = \"HotTrackingColor\" ,.dark = 0x00B77878, .light = 0x00CC6600 },\n    { .name = \"InactiveTitle\"    ,.dark = 0x002B2B2B, .light = 0x00DBCDBF },\n    { .name = \"InactiveTitleText\",.dark = 0x00969696, .light = 0x00000000 },\n    { .name = \"MenuHilight\"      ,.dark = 0x00002642, .light = 0x00FF9933 },\n    { .name = \"TitleText\"        ,.dark = 0x00FFFFFF, .light = 0x00000000 },\n//  { .name = \"Window\"           ,.dark = 0x00000000, .light = 0x00FFFFFF }, // too contrast\n//  { .name = \"Window\"           ,.dark = 0x00121212, .light = 0x00E0E0E0 },\n    { .name = \"Window\"           ,.dark = 0x002E2E2E, .light = 0x00E0E0E0 },\n    { .name = \"WindowText\"       ,.dark = 0x00FFFFFF, .light = 0x00000000 },\n};\n\n// TODO: add\n// Accent Color BGR: B16300  RGB: 0063B1 light blue\n// [HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM]\n// \"AccentColor\"=dword:ffb16300\n// Windows used as accent almost on everything\n// see here: https://github.com/leok7v/ui/discussions/5\n\n\nstatic ui_color_t ui_colors_get_color(int32_t color_id) {\n    // SysGetColor() does not work on Win10\n    rt_swear(0 < color_id && color_id < rt_countof(ui_theme_colors));\n    return ui_theme.is_app_dark() ?\n           ui_theme_colors[color_id].dark :\n           ui_theme_colors[color_id].light;\n}\n\nui_colors_if ui_colors = {\n    .get_color                = ui_colors_get_color,\n    .rgb_to_hsi               = ui_color_rgb_to_hsi,\n    .hsi_to_rgb               = ui_color_hsi_to_rgb,\n    .interpolate              = ui_color_interpolate,\n    .gray_with_same_intensity = ui_color_gray_with_same_intensity,\n    .lighten                  = ui_color_lighten,\n    .darken                   = ui_color_darken,\n    .adjust_saturation        = ui_color_adjust_saturation,\n    .multiply_brightness      = ui_color_brightness,\n    .multiply_saturation      = ui_color_saturation,\n    .transparent      = ui_color_transparent,\n    .none             = (ui_color_t)0xFFFFFFFFU, // aka CLR_INVALID in wingdi\n    .text             = ui_color_rgb(240, 231, 220),\n    .white            = ui_color_rgb(255, 255, 255),\n    .black            = ui_color_rgb(0,     0,   0),\n    .red              = ui_color_rgb(255,   0,   0),\n    .green            = ui_color_rgb(0,   255,   0),\n    .blue             = ui_color_rgb(0,   0,   255),\n    .yellow           = ui_color_rgb(255, 255,   0),\n    .cyan             = ui_color_rgb(0,   255, 255),\n    .magenta          = ui_color_rgb(255,   0, 255),\n    .gray             = ui_color_rgb(128, 128, 128),\n    // tone down RGB colors:\n    .tone_white       = ui_color_rgb(164, 164, 164),\n    .tone_red         = ui_color_rgb(192,  64,  64),\n    .tone_green       = ui_color_rgb(64,  192,  64),\n    .tone_blue        = ui_color_rgb(64,   64, 192),\n    .tone_yellow      = ui_color_rgb(192, 192,  64),\n    .tone_cyan        = ui_color_rgb(64,  192, 192),\n    .tone_magenta     = ui_color_rgb(192,  64, 192),\n    // miscellaneous:\n    .orange           = ui_color_rgb(255, 165,   0), // 0xFFA500\n    .dark_green          = ui_color_rgb(  1,  50,  32), // 0x013220\n    .pink             = ui_color_rgb(255, 192, 203), // 0xFFC0CB\n    .ochre            = ui_color_rgb(204, 119,  34), // 0xCC7722\n    .gold             = ui_color_rgb(255, 215,   0), // 0xFFD700\n    .teal             = ui_color_rgb(  0, 128, 128), // 0x008080\n    .wheat            = ui_color_rgb(245, 222, 179), // 0xF5DEB3\n    .tan              = ui_color_rgb(210, 180, 140), // 0xD2B48C\n    .brown            = ui_color_rgb(165,  42,  42), // 0xA52A2A\n    .maroon           = ui_color_rgb(128,   0,   0), // 0x800000\n    .barbie_pink      = ui_color_rgb(224,  33, 138), // 0xE0218A\n    .steel_pink       = ui_color_rgb(204,  51, 204), // 0xCC33CC\n    .salmon_pink      = ui_color_rgb(255, 145, 164), // 0xFF91A4\n    .gainsboro        = ui_color_rgb(220, 220, 220), // 0xDCDCDC\n    .light_gray       = ui_color_rgb(211, 211, 211), // 0xD3D3D3\n    .silver           = ui_color_rgb(192, 192, 192), // 0xC0C0C0\n    .dark_gray        = ui_color_rgb(169, 169, 169), // 0xA9A9A9\n    .dim_gray         = ui_color_rgb(105, 105, 105), // 0x696969\n    .light_slate_gray = ui_color_rgb(119, 136, 153), // 0x778899\n    .slate_gray       = ui_color_rgb(112, 128, 144), // 0x708090\n    /* Main Panel Backgrounds */\n    .ennui_black                = ui_color_rgb( 18,  18,  18), // 0x1212121\n    .charcoal                   = ui_color_rgb( 54,  69,  79), // 0x36454F\n    .onyx                       = ui_color_rgb( 53,  56,  57), // 0x353839\n    .gunmetal                   = ui_color_rgb( 42,  52,  57), // 0x2A3439\n    .jet_black                  = ui_color_rgb( 52,  52,  52), // 0x343434\n    .outer_space                = ui_color_rgb( 65,  74,  76), // 0x414A4C\n    .eerie_black                = ui_color_rgb( 27,  27,  27), // 0x1B1B1B\n    .oil                        = ui_color_rgb( 59,  60,  54), // 0x3B3C36\n    .black_coral                = ui_color_rgb( 84,  98, 111), // 0x54626F\n    .obsidian                   = ui_color_rgb( 58,  50,  45), // 0x3A322D\n    /* Secondary Panels or Sidebars */\n    .raisin_black               = ui_color_rgb( 39,  38,  53), // 0x272635\n    .dark_charcoal              = ui_color_rgb( 48,  48,  48), // 0x303030\n    .dark_jungle_green          = ui_color_rgb( 26,  36,  33), // 0x1A2421\n    .pine_tree                  = ui_color_rgb( 42,  47,  35), // 0x2A2F23\n    .rich_black                 = ui_color_rgb(  0,  64,  64), // 0x004040\n    .eclipse                    = ui_color_rgb( 63,  57,  57), // 0x3F3939\n    .cafe_noir                  = ui_color_rgb( 75,  54,  33), // 0x4B3621\n\n    /* Flat Buttons */\n    .prussian_blue              = ui_color_rgb(  0,  49,  83), // 0x003153\n    .midnight_green             = ui_color_rgb(  0,  73,  83), // 0x004953\n    .charleston_green           = ui_color_rgb( 35,  43,  43), // 0x232B2B\n    .rich_black_fogra           = ui_color_rgb( 10,  15,  13), // 0x0A0F0D\n    .dark_liver                 = ui_color_rgb( 83,  75,  79), // 0x534B4F\n    .dark_slate_gray            = ui_color_rgb( 47,  79,  79), // 0x2F4F4F\n    .black_olive                = ui_color_rgb( 59,  60,  54), // 0x3B3C36\n    .cadet                      = ui_color_rgb( 83, 104, 114), // 0x536872\n\n    /* Button highlights (hover) */\n    .dark_sienna                = ui_color_rgb( 60,  20,  20), // 0x3C1414\n    .bistre_brown               = ui_color_rgb(150, 113,  23), // 0x967117\n    .dark_puce                  = ui_color_rgb( 79,  58,  60), // 0x4F3A3C\n    .wenge                      = ui_color_rgb(100,  84,  82), // 0x645452\n\n    /* Raised button effects */\n    .dark_scarlet               = ui_color_rgb( 86,   3,  25), // 0x560319\n    .burnt_umber                = ui_color_rgb(138,  51,  36), // 0x8A3324\n    .caput_mortuum              = ui_color_rgb( 89,  39,  32), // 0x592720\n    .barn_red                   = ui_color_rgb(124,  10,   2), // 0x7C0A02\n\n    /* Text and Icons */\n    .platinum                   = ui_color_rgb(229, 228, 226), // 0xE5E4E2\n    .anti_flash_white           = ui_color_rgb(242, 243, 244), // 0xF2F3F4\n    .silver_sand                = ui_color_rgb(191, 193, 194), // 0xBFC1C2\n    .quick_silver               = ui_color_rgb(166, 166, 166), // 0xA6A6A6\n\n    /* Links and Selections */\n    .dark_powder_blue           = ui_color_rgb(  0,  51, 153), // 0x003399\n    .sapphire_blue              = ui_color_rgb( 15,  82, 186), // 0x0F52BA\n    .international_klein_blue   = ui_color_rgb(  0,  47, 167), // 0x002FA7\n    .zaffre                     = ui_color_rgb(  0,  20, 168), // 0x0014A8\n\n    /* Additional Colors */\n    .fish_belly                 = ui_color_rgb(232, 241, 212), // 0xE8F1D4\n    .rusty_red                  = ui_color_rgb(218,  44,  67), // 0xDA2C43\n    .falu_red                   = ui_color_rgb(128,  24,  24), // 0x801818\n    .cordovan                   = ui_color_rgb(137,  63,  69), // 0x893F45\n    .dark_raspberry             = ui_color_rgb(135,  38,  87), // 0x872657\n    .deep_magenta               = ui_color_rgb(204,   0, 204), // 0xCC00CC\n    .byzantium                  = ui_color_rgb(112,  41,  99), // 0x702963\n    .amethyst                   = ui_color_rgb(153, 102, 204), // 0x9966CC\n    .wisteria                   = ui_color_rgb(201, 160, 220), // 0xC9A0DC\n    .lavender_purple            = ui_color_rgb(150, 123, 182), // 0x967BB6\n    .opera_mauve                = ui_color_rgb(183, 132, 167), // 0xB784A7\n    .mauve_taupe                = ui_color_rgb(145,  95, 109), // 0x915F6D\n    .rich_lavender              = ui_color_rgb(167, 107, 207), // 0xA76BCF\n    .pansy_purple               = ui_color_rgb(120,  24,  74), // 0x78184A\n    .violet_eggplant            = ui_color_rgb(153,  17, 153), // 0x991199\n    .jazzberry_jam              = ui_color_rgb(165,  11,  94), // 0xA50B5E\n    .dark_orchid                = ui_color_rgb(153,  50, 204), // 0x9932CC\n    .electric_purple            = ui_color_rgb(191,   0, 255), // 0xBF00FF\n    .sky_magenta                = ui_color_rgb(207, 113, 175), // 0xCF71AF\n    .brilliant_rose             = ui_color_rgb(230, 103, 206), // 0xE667CE\n    .fuchsia_purple             = ui_color_rgb(204,  57, 123), // 0xCC397B\n    .french_raspberry           = ui_color_rgb(199,  44,  72), // 0xC72C48\n    .wild_watermelon            = ui_color_rgb(252, 108, 133), // 0xFC6C85\n    .neon_carrot                = ui_color_rgb(255, 163,  67), // 0xFFA343\n    .burnt_orange               = ui_color_rgb(204,  85,   0), // 0xCC5500\n    .carrot_orange              = ui_color_rgb(237, 145,  33), // 0xED9121\n    .tiger_orange               = ui_color_rgb(253, 106,   2), // 0xFD6A02\n    .giant_onion                = ui_color_rgb(176, 181, 137), // 0xB0B589\n    .rust                       = ui_color_rgb(183,  65,  14), // 0xB7410E\n    .copper_red                 = ui_color_rgb(203, 109,  81), // 0xCB6D51\n    .dark_tangerine             = ui_color_rgb(255, 168,  18), // 0xFFA812\n    .bright_marigold            = ui_color_rgb(252, 192,   6), // 0xFCC006\n    .bone                       = ui_color_rgb(227, 218, 201), // 0xE3DAC9\n\n    /* Earthy Tones */\n    .sienna                     = ui_color_rgb(160,  82,  45), // 0xA0522D\n    .sandy_brown                = ui_color_rgb(244, 164,  96), // 0xF4A460\n    .golden_brown               = ui_color_rgb(153, 101,  21), // 0x996515\n    .camel                      = ui_color_rgb(193, 154, 107), // 0xC19A6B\n    .burnt_sienna               = ui_color_rgb(238, 124,  88), // 0xEE7C58\n    .khaki                      = ui_color_rgb(195, 176, 145), // 0xC3B091\n    .dark_khaki                 = ui_color_rgb(189, 183, 107), // 0xBDB76B\n\n    /* Greens */\n    .fern_green                 = ui_color_rgb( 79, 121,  66), // 0x4F7942\n    .moss_green                 = ui_color_rgb(138, 154,  91), // 0x8A9A5B\n    .myrtle_green               = ui_color_rgb( 49, 120, 115), // 0x317873\n    .pine_green                 = ui_color_rgb(  1, 121, 111), // 0x01796F\n    .jungle_green               = ui_color_rgb( 41, 171, 135), // 0x29AB87\n    .sacramento_green           = ui_color_rgb(  4,  57,  39), // 0x043927\n\n    /* Blues */\n    .yale_blue                  = ui_color_rgb( 15,  77, 146), // 0x0F4D92\n    .cobalt_blue                = ui_color_rgb(  0,  71, 171), // 0x0047AB\n    .persian_blue               = ui_color_rgb( 28,  57, 187), // 0x1C39BB\n    .royal_blue                 = ui_color_rgb( 65, 105, 225), // 0x4169E1\n    .iceberg                    = ui_color_rgb(113, 166, 210), // 0x71A6D2\n    .blue_yonder                = ui_color_rgb( 80, 114, 167), // 0x5072A7\n\n    /* Miscellaneous */\n    .cocoa_brown                = ui_color_rgb(210, 105,  30), // 0xD2691E\n    .cinnamon_satin             = ui_color_rgb(205,  96, 126), // 0xCD607E\n    .fallow                     = ui_color_rgb(193, 154, 107), // 0xC19A6B\n    .cafe_au_lait               = ui_color_rgb(166, 123,  91), // 0xA67B5B\n    .liver                      = ui_color_rgb(103,  76,  71), // 0x674C47\n    .shadow                     = ui_color_rgb(138, 121,  93), // 0x8A795D\n    .cool_grey                  = ui_color_rgb(140, 146, 172), // 0x8C92AC\n    .payne_grey                 = ui_color_rgb( 83, 104, 120), // 0x536878\n\n    /* Lighter Tones for Contrast */\n    .timberwolf                 = ui_color_rgb(219, 215, 210), // 0xDBD7D2\n    .silver_chalice             = ui_color_rgb(172, 172, 172), // 0xACACAC\n    .roman_silver               = ui_color_rgb(131, 137, 150), // 0x838996\n\n    /* Dark Mode Specific Highlights */\n    .electric_lavender          = ui_color_rgb(244, 191, 255), // 0xF4BFFF\n    .magenta_haze               = ui_color_rgb(159,  69, 118), // 0x9F4576\n    .cyber_grape                = ui_color_rgb( 88,  66, 124), // 0x58427C\n    .purple_navy                = ui_color_rgb( 78,  81, 128), // 0x4E5180\n    .liberty                    = ui_color_rgb( 84,  90, 167), // 0x545AA7\n    .purple_mountain_majesty    = ui_color_rgb(150, 120, 182), // 0x9678B6\n    .ceil                       = ui_color_rgb(146, 161, 207), // 0x92A1CF\n    .moonstone_blue             = ui_color_rgb(115, 169, 194), // 0x73A9C2\n    .independence               = ui_color_rgb( 76,  81, 109)  // 0x4C516D\n};\n\n// _____________________________ ui_containers.c ______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n\nstatic bool ui_containers_debug;\n\n#pragma push_macro(\"debugln\")\n#pragma push_macro(\"ui_layout_dump\")\n#pragma push_macro(\"ui_layout_enter\")\n#pragma push_macro(\"ui_layout_exit\")\n\n// Usage of: ui_view_for_each_begin(p, c) { ... } ui_view_for_each_end(p, c)\n// makes code inside iterator debugger friendly and ensures correct __LINE__\n\n#define debugln(...) do {                                   \\\n    if (ui_containers_debug) {  rt_println(__VA_ARGS__); }  \\\n} while (0)\n\nstatic int32_t ui_layout_nesting;\n\n#define ui_layout_enter(v) do {                                         \\\n    ui_ltrb_t i_ = ui_view.margins(v, &v->insets);                      \\\n    ui_ltrb_t p_ = ui_view.margins(v, &v->padding);                     \\\n    debugln(\"%*c> %4d,%-4d %4dx%-4d p: %d %d %d %d i: %d %d %d %d %s\",  \\\n            ui_layout_nesting, 0x20,                                    \\\n            v->x, v->y, v->w, v->h,                                     \\\n            p_.left, p_.top, p_.right, p_.bottom,                       \\\n            i_.left, i_.top, i_.right, i_.bottom,                       \\\n            ui_view_debug_id(v));                                       \\\n    ui_layout_nesting += 4;                                             \\\n} while (0)\n\n#define ui_layout_exit(v) do {                                          \\\n    ui_layout_nesting -= 4;                                             \\\n    debugln(\"%*c< %4d,%-4d %4dx%-4d %s\",                                \\\n            ui_layout_nesting, 0x20,                                    \\\n            v->x, v->y, v->w, v->h, ui_view_debug_id(v));               \\\n} while (0)\n\n#define ui_layout_clild(v) do {                                         \\\n    debugln(\"%*c %4d,%-4d %4dx%-4d %s\", ui_layout_nesting, 0x20,        \\\n            c->x, c->y, c->w, c->h, ui_view_debug_id(v));               \\\n} while (0)\n\nstatic const char* ui_stack_finite_int(int32_t v, char* text, int32_t count) {\n    rt_swear(v >= 0);\n    if (v == ui.infinity) {\n        rt_str.format(text, count, \"%s\", rt_glyph_infinity);\n    } else {\n        rt_str.format(text, count, \"%d\", v);\n    }\n    return text;\n}\n\n#define ui_layout_dump(v) do {                                                \\\n    char maxw[32];                                                            \\\n    char maxh[32];                                                            \\\n    debugln(\"%s[%4.4s] %4d,%-4d %4dx%-4d, max[%sx%s] \"                        \\\n        \"padding { %.3f %.3f %.3f %.3f } \"                                    \\\n        \"insets { %.3f %.3f %.3f %.3f } align: 0x%02X\",                       \\\n        ui_view_debug_id(v),                                                  \\\n        &v->type, v->x, v->y, v->w, v->h,                                     \\\n        ui_stack_finite_int(v->max_w, maxw, rt_countof(maxw)),                \\\n        ui_stack_finite_int(v->max_h, maxh, rt_countof(maxh)),                \\\n        v->padding.left, v->padding.top, v->padding.right, v->padding.bottom, \\\n        v->insets.left, v->insets.top, v->insets.right, v->insets.bottom,     \\\n        v->align);                                                            \\\n} while (0)\n\nstatic void ui_span_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_span, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_ltrb_t insets;\n    ui_view.inbox(p, null, &insets);\n    int32_t w = insets.left;\n    int32_t h = 0;\n    int32_t max_w = w;\n    ui_view_for_each_begin(p, c) {\n        rt_swear(c->max_w == 0 || c->max_w >= c->w,\n              \"max_w: %d w: %d\", c->max_w, c->w);\n        if (ui_view.is_hidden(c)) {\n            // nothing\n        } else if (c->type == ui_view_spacer) {\n            c->padding = (ui_margins_t){ 0, 0, 0, 0 };\n            c->w = 0; // layout will distribute excess here\n            c->h = 0; // starts with zero\n            max_w = ui.infinity; // spacer make width greedy\n        } else {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            h = rt_max(h, cbx.h);\n            if (c->max_w == ui.infinity) {\n                max_w = ui.infinity;\n            } else if (max_w < ui.infinity && c->max_w != 0) {\n                rt_swear(c->max_w >= c->w, \"c->max_w %d < c->w %d \",\n                      c->max_w, c->w);\n                max_w += c->max_w;\n            } else if (max_w < ui.infinity) {\n                rt_swear(0 <= max_w + cbx.w &&\n                      (int64_t)max_w + (int64_t)cbx.w < (int64_t)ui.infinity,\n                      \"max_w:%d + cbx.w:%d = %d\", max_w, cbx.w, max_w + cbx.w);\n                max_w += cbx.w;\n            }\n            w += cbx.w;\n        }\n        ui_layout_clild(c);\n    } ui_view_for_each_end(p, c);\n    if (0 < max_w && max_w < ui.infinity) {\n        rt_swear(0 <= max_w + insets.right &&\n              (int64_t)max_w + (int64_t)insets.right < (int64_t)ui.infinity,\n             \"max_w:%d + right:%d = %d\", max_w, insets.right, max_w + insets.right);\n        max_w += insets.right;\n    }\n    rt_swear(max_w == 0 || max_w >= w, \"max_w: %d w: %d\", max_w, w);\n    if (ui_view.is_hidden(p)) {\n        p->w = 0;\n        p->h = 0;\n    } else {\n        p->w = w + insets.right;\n        p->h = insets.top + h + insets.bottom;\n        rt_swear(p->max_w == 0 || p->max_w >= p->w,\n              \"max_w: %d is less than actual width: %d\", p->max_w, p->w);\n    }\n    ui_layout_exit(p);\n}\n\n// after measure of the subtree is concluded the parent ui_span\n// may adjust span_w wider number depending on it's own width\n// and ui_span.max_w agreement\n\nstatic int32_t ui_span_place_child(ui_view_t* c, ui_rect_t pbx, int32_t x) {\n    ui_ltrb_t padding = ui_view.margins(c, &c->padding);\n    // setting child`s max_h to infinity means that child`s height is\n    // *always* fill vertical view size of the parent\n    // childs.h can exceed parent.h (vertical overflow) - is not\n    // encouraged but allowed\n    if (c->max_h == ui.infinity) {\n        // important c->h changed, cbx.h is no longer valid\n        c->h = rt_max(c->h, pbx.h - padding.top - padding.bottom);\n    }\n    int32_t min_y = pbx.y + padding.top;\n    if ((c->align & ui.align.top) != 0) {\n        rt_assert(c->align == ui.align.top);\n        c->y = min_y;\n    } else if ((c->align & ui.align.bottom) != 0) {\n        rt_assert(c->align == ui.align.bottom);\n        c->y = rt_max(min_y, pbx.y + pbx.h - c->h - padding.bottom);\n    } else { // effective height (c->h might have been changed)\n        rt_assert(c->align == ui.align.center,\n                  \"only top, center, bottom alignment for span\");\n        const int32_t ch = padding.top + c->h + padding.bottom;\n        c->y = rt_max(min_y, pbx.y + (pbx.h - ch) / 2 + padding.top);\n    }\n    c->x = x + padding.left;\n    return c->x + c->w + padding.right;\n}\n\nstatic void ui_span_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_span, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t spacers = 0; // Number of spacers\n    int32_t max_w_count = 0;\n    int32_t x = p->x + insets.left;\n    ui_view_for_each_begin(p, c) {\n        if (!ui_view.is_hidden(c)) {\n            if (c->type == ui_view_spacer) {\n                c->x = x;\n                c->y = pbx.y;\n                c->h = pbx.h;\n                c->w = 0;\n                spacers++;\n            } else {\n                x = ui_span_place_child(c, pbx, x);\n                rt_swear(c->max_w == 0 || c->max_w >= c->w,\n                      \"max_w:%d < w:%d\", c->max_w, c->w);\n                if (c->max_w > 0) {\n                    max_w_count++;\n                }\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    int32_t xw = rt_max(0, pbx.x + pbx.w - x); // excess width\n    int32_t max_w_sum = 0;\n    if (xw > 0 && max_w_count > 0) {\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c) && c->type != ui_view_spacer &&\n                 c->max_w > 0) {\n                max_w_sum += rt_min(c->max_w, xw);\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    if (xw > 0 && max_w_count > 0) {\n        debugln(\"%*c pass 2: fill parent\", ui_layout_nesting, 0x20);\n        x = p->x + insets.left;\n        int32_t k = 0;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    rt_swear(padding.left == 0 && padding.right == 0);\n                } else if (c->max_w > 0) {\n                    const int32_t max_w = rt_min(c->max_w, xw);\n                    int64_t proportional = (xw * (int64_t)max_w) / max_w_sum;\n                    rt_assert(proportional <= (int64_t)INT32_MAX);\n                    int32_t cw = (int32_t)proportional;\n                    c->w = rt_min(c->max_w, c->w + cw);\n                    k++;\n                }\n                // TODO: take into account .align of a child and adjust x\n                //       depending on ui.align.left/right/center\n                //       distributing excess width on the left and right of a child\n                c->x = padding.left + x;\n                x = c->x + padding.left + c->w + padding.right;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n        rt_swear(k == max_w_count);\n    }\n    // excess width after max_w of non-spacers taken into account\n    xw = rt_max(0, pbx.x + pbx.w - x);\n    if (xw > 0 && spacers > 0) {\n        // evenly distribute excess among spacers\n        debugln(\"%*c pass 3: expand spacers\", ui_layout_nesting, 0x20);\n        int32_t partial = xw / spacers;\n        x = p->x + insets.left;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    c->y = pbx.y;\n                    c->w = partial;\n                    c->h = pbx.h;\n                    spacers--;\n                }\n                c->x = x + padding.left;\n                x = c->x + c->w + padding.right;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    ui_layout_exit(p);\n}\n\nstatic void ui_list_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_list, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t max_h = insets.top;\n    int32_t h = insets.top;\n    int32_t w = 0;\n    ui_view_for_each_begin(p, c) {\n        rt_swear(c->max_h == 0 || c->max_h >= c->h, \"max_h: %d h: %d\",\n              c->max_h, c->h);\n        if (!ui_view.is_hidden(c)) {\n            if (c->type == ui_view_spacer) {\n                c->padding = (ui_margins_t){ 0, 0, 0, 0 };\n                c->h = 0; // layout will distribute excess here\n                max_h = ui.infinity; // spacer make height greedy\n            } else {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                w = rt_max(w, cbx.w);\n                if (c->max_h == ui.infinity) {\n                    max_h = ui.infinity;\n                } else if (max_h < ui.infinity && c->max_h != 0) {\n                    rt_swear(c->max_h >= c->h, \"c->max_h:%d < c->h: %d\",\n                          c->max_h, c->h);\n                    max_h += c->max_h;\n                } else if (max_h < ui.infinity) {\n                    rt_swear(0 <= max_h + cbx.h &&\n                          (int64_t)max_h + (int64_t)cbx.h < (int64_t)ui.infinity,\n                          \"max_h:%d + ch:%d = %d\", max_h, cbx.h, max_h + cbx.h);\n                    max_h += cbx.h;\n                }\n                h += cbx.h;\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    if (max_h < ui.infinity) {\n        rt_swear(0 <= max_h + insets.bottom &&\n              (int64_t)max_h + (int64_t)insets.bottom < (int64_t)ui.infinity,\n             \"max_h:%d + bottom:%d = %d\",\n              max_h, insets.bottom, max_h + insets.bottom);\n        max_h += insets.bottom;\n    }\n    if (ui_view.is_hidden(p)) {\n        p->w = 0;\n        p->h = 0;\n    } else if (p == ui_app.root) {\n        // ui_app.root is special occupying whole window client rectangle\n        // sans borders and caption thus it should not be re-measured\n    } else {\n        p->h = h + insets.bottom;\n        p->w = insets.left + w + insets.right;\n    }\n    ui_layout_exit(p);\n}\n\nstatic int32_t ui_list_place_child(ui_view_t* c, ui_rect_t pbx, int32_t y) {\n    ui_ltrb_t padding = ui_view.margins(c, &c->padding);\n    // setting child`s max_w to infinity means that child`s height is\n    // *always* fill vertical view size of the parent\n    // childs.w can exceed parent.w (horizontal overflow) - not encouraged but allowed\n    if (c->max_w == ui.infinity) {\n        c->w = rt_max(c->w, pbx.w - padding.left - padding.right);\n    }\n    int32_t min_x = pbx.x + padding.left;\n    if ((c->align & ui.align.left) != 0) {\n        rt_assert(c->align == ui.align.left);\n        c->x = min_x;\n    } else if ((c->align & ui.align.right) != 0) {\n        rt_assert(c->align == ui.align.right);\n        c->x = rt_max(min_x, pbx.x + pbx.w - c->w - padding.right);\n    } else {\n        rt_assert(c->align == ui.align.center,\n                  \"only left, center, right, alignment for list\");\n        const int32_t cw = padding.left + c->w + padding.right;\n        c->x = rt_max(min_x, pbx.x + (pbx.w - cw) / 2 + padding.left);\n    }\n    c->y = y + padding.top;\n    return c->y + c->h + padding.bottom;\n}\n\nstatic void ui_list_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_list, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t spacers = 0; // Number of spacers\n    int32_t max_h_sum = 0;\n    int32_t max_h_count = 0;\n    int32_t y = pbx.y;\n    ui_view_for_each_begin(p, c) {\n        if (ui_view.is_hidden(c)) {\n            // nothing\n        } else if (c->type == ui_view_spacer) {\n            c->x = pbx.x;\n            c->y = y;\n            c->w = pbx.w;\n            c->h = 0;\n            spacers++;\n        } else {\n            y = ui_list_place_child(c, pbx, y);\n            rt_swear(c->max_h == 0 || c->max_h >= c->h,\n                  \"max_h:%d < h:%d\", c->max_h, c->h);\n            if (c->max_h > 0) {\n                // clamp max_h to the effective parent height\n                max_h_count++;\n            }\n        }\n    } ui_view_for_each_end(p, c);\n    int32_t xh = rt_max(0, pbx.y + pbx.h - y); // excess height\n    if (xh > 0 && max_h_count > 0) {\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c) && c->type != ui_view_spacer &&\n                 c->max_h > 0) {\n                max_h_sum += rt_min(c->max_h, xh);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    if (xh > 0 && max_h_count > 0) {\n        debugln(\"%*c pass 2: fill parent\", ui_layout_nesting, 0x20);\n        y = pbx.y;\n        int32_t k = 0;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type != ui_view_spacer && c->max_h > 0) {\n                    const int32_t max_h = rt_min(c->max_h, xh);\n                    int64_t proportional = (xh * (int64_t)max_h) / max_h_sum;\n                    rt_assert(proportional <= (int64_t)INT32_MAX);\n                    int32_t ch = (int32_t)proportional;\n                    c->h = rt_min(c->max_h, c->h + ch);\n                    k++;\n                }\n                int32_t ch = padding.top + c->h + padding.bottom;\n                c->y = y + padding.top;\n                y += ch;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n        rt_swear(k == max_h_count);\n    }\n    // excess height after max_h of non-spacers taken into account\n    xh = rt_max(0, pbx.y + pbx.h - y); // excess height\n    if (xh > 0 && spacers > 0) {\n        // evenly distribute excess among spacers\n        debugln(\"%*c pass 3: expand spacers\", ui_layout_nesting, 0x20);\n        int32_t partial = xh / spacers;\n        y = pbx.y;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    c->x = pbx.x;\n                    c->w = pbx.x + pbx.w - pbx.x;\n                    c->h = partial; // TODO: last?\n                    spacers--;\n                }\n                int32_t ch = padding.top + c->h + padding.bottom;\n                c->y = y + padding.top;\n                y += ch;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    ui_layout_exit(p);\n}\n\nstatic void ui_stack_child_3x3(ui_view_t* c, int32_t *row, int32_t *col) {\n    *row = 0; *col = 0; // makes code analysis happier\n    if (c->align == (ui.align.left|ui.align.top)) {\n        *row = 0; *col = 0;\n    } else if (c->align == ui.align.top) {\n        *row = 0; *col = 1;\n    } else if (c->align == (ui.align.right|ui.align.top)) {\n        *row = 0; *col = 2;\n    } else if (c->align == ui.align.left) {\n        *row = 1; *col = 0;\n    } else if (c->align == ui.align.center) {\n        *row = 1; *col = 1;\n    } else if (c->align == ui.align.right) {\n        *row = 1; *col = 2;\n    } else if (c->align == (ui.align.left|ui.align.bottom)) {\n        *row = 2; *col = 0;\n    } else if (c->align == ui.align.bottom) {\n        *row = 2; *col = 1;\n    } else if (c->align == (ui.align.right|ui.align.bottom)) {\n        *row = 2; *col = 2;\n    } else {\n        rt_swear(false, \"invalid child align: 0x%02X\", c->align);\n    }\n}\n\nstatic void ui_stack_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_stack, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    ui_wh_t sides[3][3] = { {0, 0} };\n    ui_view_for_each_begin(p, c) {\n        if (!ui_view.is_hidden(c)) {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            int32_t row = 0;\n            int32_t col = 0;\n            ui_stack_child_3x3(c, &row, &col);\n            sides[row][col].w = rt_max(sides[row][col].w, cbx.w);\n            sides[row][col].h = rt_max(sides[row][col].h, cbx.h);\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    if (ui_containers_debug) {\n        for (int32_t r = 0; r < rt_countof(sides); r++) {\n            char text[1024];\n            text[0] = 0;\n            for (int32_t c = 0; c < rt_countof(sides[r]); c++) {\n                char line[128];\n                rt_str_printf(line, \" %4dx%-4d\", sides[r][c].w, sides[r][c].h);\n                strcat(text, line);\n            }\n            debugln(\"%*c sides[%d] %s\", ui_layout_nesting, 0x20, r, text);\n        }\n    }\n    ui_wh_t wh = {0, 0};\n    for (int32_t r = 0; r < 3; r++) {\n        int32_t sum_w = 0;\n        for (int32_t c = 0; c < 3; c++) {\n            sum_w += sides[r][c].w;\n        }\n        wh.w = rt_max(wh.w, sum_w);\n    }\n    for (int32_t c = 0; c < 3; c++) {\n        int32_t sum_h = 0;\n        for (int32_t r = 0; r < 3; r++) {\n            sum_h += sides[r][c].h;\n        }\n        wh.h = rt_max(wh.h, sum_h);\n    }\n    debugln(\"%*c wh %4dx%-4d\", ui_layout_nesting, 0x20, wh.w, wh.h);\n    p->w = insets.left + wh.w + insets.right;\n    p->h = insets.top  + wh.h + insets.bottom;\n    ui_layout_exit(p);\n}\n\nstatic void ui_stack_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_stack, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    ui_view_for_each_begin(p, c) {\n        if (c->type != ui_view_spacer && !ui_view.is_hidden(c)) {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            const int32_t pw = p->w - insets.left - insets.right - padding.left - padding.right;\n            const int32_t ph = p->h - insets.top - insets.bottom - padding.top - padding.bottom;\n            int32_t cw = c->max_w == ui.infinity ? pw : c->max_w;\n            if (cw > 0) {\n                c->w = rt_min(cw, pw);\n            }\n            int32_t ch = c->max_h == ui.infinity ? ph : c->max_h;\n            if (ch > 0) {\n                c->h = rt_min(ch, ph);\n            }\n            rt_swear((c->align & (ui.align.left|ui.align.right)) !=\n                               (ui.align.left|ui.align.right),\n                   \"align: left|right 0x%02X\", c->align);\n            rt_swear((c->align & (ui.align.top|ui.align.bottom)) !=\n                               (ui.align.top|ui.align.bottom),\n                   \"align: top|bottom 0x%02X\", c->align);\n            int32_t min_x = pbx.x + padding.left;\n            if ((c->align & ui.align.left) != 0) {\n                c->x = min_x;\n            } else if ((c->align & ui.align.right) != 0) {\n                c->x = rt_max(min_x, pbx.x + pbx.w - c->w - padding.right);\n            } else {\n                c->x = rt_max(min_x, min_x + (pbx.w - (padding.left + c->w + padding.right)) / 2);\n            }\n            int32_t min_y = pbx.y + padding.top;\n            if ((c->align & ui.align.top) != 0) {\n                c->y = min_y;\n            } else if ((c->align & ui.align.bottom) != 0) {\n                c->y = rt_max(min_y, pbx.y + pbx.h - c->h - padding.bottom);\n            } else {\n                c->y = rt_max(min_y, min_y + (pbx.h - (padding.top + c->h + padding.bottom)) / 2);\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    ui_layout_exit(p);\n}\n\nstatic void ui_container_paint(ui_view_t* v) {\n    if (!ui_color_is_undefined(v->background) &&\n        !ui_color_is_transparent(v->background)) {\n        ui_gdi.fill(v->x, v->y, v->w, v->h, v->background);\n    } else {\n//      rt_println(\"%s undefined\", ui_view_debug_id(v));\n    }\n}\n\nstatic void ui_view_container_init(ui_view_t* v) {\n    v->background = ui_colors.transparent;\n    v->insets  = (ui_margins_t){\n       .left  = 0.25, .top    = 0.125,\n        .right = 0.25, .bottom = 0.125\n//      .left  = 0.25, .top    = 0.0625,  // TODO: why?\n//      .right = 0.25, .bottom = 0.1875\n    };\n}\n\nvoid ui_view_init_span(ui_view_t* v) {\n    rt_swear(v->type == ui_view_span, \"type %4.4s 0x%08X\", &v->type, v->type);\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_span_measure; }\n    if (v->layout  == null) { v->layout  = ui_span_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_span\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_span\"; }\n}\n\nvoid ui_view_init_list(ui_view_t* v) {\n    rt_swear(v->type == ui_view_list, \"type %4.4s 0x%08X\", &v->type, v->type);\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_list_measure; }\n    if (v->layout  == null) { v->layout  = ui_list_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_list\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_list\"; }\n}\n\nvoid ui_view_init_spacer(ui_view_t* v) {\n    rt_swear(v->type == ui_view_spacer, \"type %4.4s 0x%08X\", &v->type, v->type);\n    v->w = 0;\n    v->h = 0;\n    v->max_w = ui.infinity;\n    v->max_h = ui.infinity;\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_spacer\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_spacer\"; }\n\n}\n\nvoid ui_view_init_stack(ui_view_t* v) {\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_stack_measure; }\n    if (v->layout  == null) { v->layout  = ui_stack_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_stack\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_stack\"; }\n}\n\n#pragma pop_macro(\"ui_layout_exit\")\n#pragma pop_macro(\"ui_layout_enter\")\n#pragma pop_macro(\"ui_layout_dump\")\n#pragma pop_macro(\"debugln\")\n// ________________________________ ui_core.c _________________________________\n\n#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n#define UI_WM_ANIMATE  (WM_APP + 0x7FFF)\n#define UI_WM_OPENING  (WM_APP + 0x7FFE)\n#define UI_WM_CLOSING  (WM_APP + 0x7FFD)\n#define UI_WM_TAP      (WM_APP + 0x7FFC)\n#define UI_WM_DTAP     (WM_APP + 0x7FFB) // double tap (aka click)\n#define UI_WM_PRESS    (WM_APP + 0x7FFA)\n\nstatic bool ui_point_in_rect(const ui_point_t* p, const ui_rect_t* r) {\n    return r->x <= p->x && p->x < r->x + r->w &&\n           r->y <= p->y && p->y < r->y + r->h;\n}\n\nstatic bool ui_intersect_rect(ui_rect_t* i, const ui_rect_t* r0,\n                                            const ui_rect_t* r1) {\n    ui_rect_t r = {0};\n    r.x = rt_max(r0->x, r1->x);  // Maximum of left edges\n    r.y = rt_max(r0->y, r1->y);  // Maximum of top edges\n    r.w = rt_min(r0->x + r0->w, r1->x + r1->w) - r.x;  // Width of overlap\n    r.h = rt_min(r0->y + r0->h, r1->y + r1->h) - r.y;  // Height of overlap\n    bool b = r.w > 0 && r.h > 0;\n    if (!b) {\n        r.w = 0;\n        r.h = 0;\n    }\n    if (i != null) { *i = r; }\n    return b;\n}\n\nstatic ui_rect_t ui_combine_rect(const ui_rect_t* r0, const ui_rect_t* r1) {\n    return (ui_rect_t) {\n        .x = rt_min(r0->x, r1->x),\n        .y = rt_min(r0->y, r1->y),\n        .w = rt_max(r0->x + r0->w, r1->x + r1->w) - rt_min(r0->x, r1->x),\n        .h = rt_max(r0->y + r0->h, r1->y + r1->h) - rt_min(r0->y, r1->y)\n    };\n}\n\nui_if ui = {\n    .point_in_rect  = ui_point_in_rect,\n    .intersect_rect = ui_intersect_rect,\n    .combine_rect   = ui_combine_rect,\n    .infinity = INT32_MAX,\n    .align = {\n        .center = 0,\n        .left   = 0x01,\n        .top    = 0x02,\n        .right  = 0x10,\n        .bottom = 0x20\n    },\n    .visibility = { // window visibility see ShowWindow link below\n        .hide      = SW_HIDE,\n        .normal    = SW_SHOWNORMAL,\n        .minimize  = SW_SHOWMINIMIZED,\n        .maximize  = SW_SHOWMAXIMIZED,\n        .normal_na = SW_SHOWNOACTIVATE,\n        .show      = SW_SHOW,\n        .min_next  = SW_MINIMIZE,\n        .min_na    = SW_SHOWMINNOACTIVE,\n        .show_na   = SW_SHOWNA,\n        .restore   = SW_RESTORE,\n        .defau1t   = SW_SHOWDEFAULT,\n        .force_min = SW_FORCEMINIMIZE\n    },\n    .message = {\n        .animate               = UI_WM_ANIMATE,\n        .opening               = UI_WM_OPENING,\n        .closing               = UI_WM_CLOSING\n    },\n    .mouse = {\n        .button = {\n            .left  = MK_LBUTTON,\n            .right = MK_RBUTTON\n        }\n    },\n    .hit_test = {\n        .error             = HTERROR,\n        .transparent       = HTTRANSPARENT,\n        .nowhere           = HTNOWHERE,\n        .client            = HTCLIENT,\n        .caption           = HTCAPTION,\n        .system_menu       = HTSYSMENU,\n        .grow_box          = HTGROWBOX,\n        .menu              = HTMENU,\n        .horizontal_scroll = HTHSCROLL,\n        .vertical_scroll   = HTVSCROLL,\n        .min_button        = HTMINBUTTON,\n        .max_button        = HTMAXBUTTON,\n        .left              = HTLEFT,\n        .right             = HTRIGHT,\n        .top               = HTTOP,\n        .top_left          = HTTOPLEFT,\n        .top_right         = HTTOPRIGHT,\n        .bottom            = HTBOTTOM,\n        .bottom_left       = HTBOTTOMLEFT,\n        .bottom_right      = HTBOTTOMRIGHT,\n        .border            = HTBORDER,\n        .object            = HTOBJECT,\n        .close             = HTCLOSE,\n        .help              = HTHELP\n    },\n    .key = {\n        .up        = VK_UP,\n        .down      = VK_DOWN,\n        .left      = VK_LEFT,\n        .right     = VK_RIGHT,\n        .home      = VK_HOME,\n        .end       = VK_END,\n        .page_up   = VK_PRIOR,\n        .page_down = VK_NEXT,\n        .insert    = VK_INSERT,\n        .del       = VK_DELETE,\n        .back      = VK_BACK,\n        .escape    = VK_ESCAPE,\n        .enter     = VK_RETURN,\n        .minus     = VK_OEM_MINUS,\n        .plus      = VK_OEM_PLUS,\n        .f1        = VK_F1,\n        .f2        = VK_F2,\n        .f3        = VK_F3,\n        .f4        = VK_F4,\n        .f5        = VK_F5,\n        .f6        = VK_F6,\n        .f7        = VK_F7,\n        .f8        = VK_F8,\n        .f9        = VK_F9,\n        .f10       = VK_F10,\n        .f11       = VK_F11,\n        .f12       = VK_F12,\n        .f13       = VK_F13,\n        .f14       = VK_F14,\n        .f15       = VK_F15,\n        .f16       = VK_F16,\n        .f17       = VK_F17,\n        .f18       = VK_F18,\n        .f19       = VK_F19,\n        .f20       = VK_F20,\n        .f21       = VK_F21,\n        .f22       = VK_F22,\n        .f23       = VK_F23,\n        .f24       = VK_F24,\n    },\n    .beep = {\n        .ok         = 0,\n        .info       = 1,\n        .question   = 2,\n        .warning    = 3,\n        .error      = 4\n    }\n};\n\n// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow\n// ______________________________ ui_edit_doc.c _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n\n#undef UI_EDIT_STR_TEST\n#undef UI_EDIT_DOC_TEST\n#undef UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n#undef UI_EDIT_DOC_TEST_PARAGRAPHS\n\n#if 0 // flip to 1 to run tests\n\n#define UI_EDIT_STR_TEST\n#define UI_EDIT_DOC_TEST\n\n#if 0 // flip to 1 to run exhausting lengthy tests\n#define UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n#define UI_EDIT_DOC_TEST_PARAGRAPHS\n#endif\n\n#endif\n\n#pragma push_macro(\"ui_edit_check_zeros\")\n#pragma push_macro(\"ui_edit_check_pg_inside_text\")\n#pragma push_macro(\"ui_edit_check_range_inside_text\")\n#pragma push_macro(\"ui_edit_pg_dump\")\n#pragma push_macro(\"ui_edit_range_dump\")\n#pragma push_macro(\"ui_edit_text_dump\")\n#pragma push_macro(\"ui_edit_doc_dump\")\n\n#define ui_edit_pg_dump(pg)                              \\\n    rt_debug.println(__FILE__, __LINE__, __func__,       \\\n                    \"pn:%d gp:%d\", (pg)->pn, (pg)->gp)\n\n#define ui_edit_range_dump(r)                            \\\n    rt_debug.println(__FILE__, __LINE__, __func__,       \\\n            \"from {pn:%d gp:%d} to {pn:%d gp:%d}\",       \\\n    (r)->from.pn, (r)->from.gp, (r)->to.pn, (r)->to.gp);\n\n#define ui_edit_text_dump(t) do {                        \\\n    for (int32_t i_ = 0; i_ < (t)->np; i_++) {           \\\n        const ui_edit_str_t* p_ = &t->ps[i_];            \\\n        rt_debug.println(__FILE__, __LINE__, __func__,   \\\n            \"ps[%d].%d: %.*s\", i_, p_->b, p_->b, p_->u); \\\n    }                                                    \\\n} while (0)\n\n// TODO: undo/redo stacks and listeners\n#define ui_edit_doc_dump(d) do {                                \\\n    for (int32_t i_ = 0; i_ < (d)->text.np; i_++) {             \\\n        const ui_edit_str_t* p_ = &(d)->text.ps[i_];            \\\n        rt_debug.println(__FILE__, __LINE__, __func__,          \\\n            \"ps[%d].b:%d.c:%d: %p %.*s\", i_, p_->b, p_->c,      \\\n            p_, p_->b, p_->u);                                  \\\n    }                                                           \\\n} while (0)\n\n\n#ifdef DEBUG\n\n// ui_edit_check_zeros only works for packed structs:\n\n#define ui_edit_check_zeros(a_, b_) do {                                    \\\n    for (int32_t i_ = 0; i_ < (int32_t)(b_); i_++) {                        \\\n        rt_assert(((const uint8_t*)(a_))[i_] == 0x00);                      \\\n    }                                                                       \\\n} while (0)\n\n#define ui_edit_check_pg_inside_text(t_, pg_)                               \\\n    rt_assert(0 <= (pg_)->pn && (pg_)->pn < (t_)->np &&                     \\\n           0 <= (pg_)->gp && (pg_)->gp <= (t_)->ps[(pg_)->pn].g)\n\n#define ui_edit_check_range_inside_text(t_, r_) do {                         \\\n    rt_assert((r_)->from.pn <= (r_)->to.pn);                                 \\\n    rt_assert((r_)->from.pn <  (r_)->to.pn || (r_)->from.gp <= (r_)->to.gp); \\\n    ui_edit_check_pg_inside_text(t_, (&(r_)->from));                         \\\n    ui_edit_check_pg_inside_text(t_, (&(r_)->to));                           \\\n} while (0)\n\n#else\n\n#define ui_edit_check_zeros(a, b)             do { } while (0)\n#define ui_edit_check_pg_inside_text(t, pg)   do { } while (0)\n#define ui_edit_check_range_inside_text(t, r) do { } while (0)\n\n#endif\n\nstatic ui_edit_range_t ui_edit_text_all_on_null(const ui_edit_text_t* t,\n        const ui_edit_range_t* range) {\n    ui_edit_range_t r;\n    if (range != null) {\n        r = *range;\n    } else {\n        rt_assert(t->np >= 1);\n        r.from.pn = 0;\n        r.from.gp = 0;\n        r.to.pn = t->np - 1;\n        r.to.gp = t->ps[r.to.pn].g;\n    }\n    return r;\n}\n\nstatic int ui_edit_range_compare(const ui_edit_pg_t pg1, const ui_edit_pg_t pg2) {\n    int64_t d = (((int64_t)pg1.pn << 32) | pg1.gp) -\n                (((int64_t)pg2.pn << 32) | pg2.gp);\n    return d < 0 ? -1 : d > 0 ? 1 : 0;\n}\n\nstatic ui_edit_range_t ui_edit_range_order(const ui_edit_range_t range) {\n    ui_edit_range_t r = range;\n    uint64_t f = ((uint64_t)r.from.pn << 32) | r.from.gp;\n    uint64_t t = ((uint64_t)r.to.pn   << 32) | r.to.gp;\n    if (ui_edit_range.compare(r.from, r.to) > 0) {\n        uint64_t swap = t; t = f; f = swap;\n        r.from.pn = (int32_t)(f >> 32);\n        r.from.gp = (int32_t)(f);\n        r.to.pn   = (int32_t)(t >> 32);\n        r.to.gp   = (int32_t)(t);\n    }\n    return r;\n}\n\nstatic ui_edit_range_t ui_edit_text_ordered(const ui_edit_text_t* t,\n        const ui_edit_range_t* r) {\n    return ui_edit_range.order(ui_edit_text.all_on_null(t, r));\n}\n\nstatic bool ui_edit_range_is_valid(const ui_edit_range_t r) {\n    if (0 <= r.from.pn && 0 <= r.to.pn &&\n        0 <= r.from.gp && 0 <= r.to.gp) {\n        ui_edit_range_t o = ui_edit_range.order(r);\n        return ui_edit_range.compare(o.from, o.to) <= 0;\n    } else {\n        return false;\n    }\n}\n\nstatic bool ui_edit_range_is_empty(const ui_edit_range_t r) {\n    return r.from.pn == r.to.pn && r.from.gp == r.to.gp;\n}\n\nstatic ui_edit_pg_t ui_edit_text_end(const ui_edit_text_t* t) {\n    return (ui_edit_pg_t){ .pn = t->np - 1, .gp = t->ps[t->np - 1].g };\n}\n\nstatic ui_edit_range_t ui_edit_text_end_range(const ui_edit_text_t* t) {\n    ui_edit_pg_t e = (ui_edit_pg_t){ .pn = t->np - 1,\n                                     .gp = t->ps[t->np - 1].g };\n    return (ui_edit_range_t){ .from = e, .to = e };\n}\n\nstatic uint64_t ui_edit_range_uint64(const ui_edit_pg_t pg) {\n    rt_assert(pg.pn >= 0 && pg.gp >= 0);\n    return ((uint64_t)pg.pn << 32) | (uint64_t)pg.gp;\n}\n\nstatic ui_edit_pg_t ui_edit_range_pg(uint64_t uint64) {\n    rt_assert((int32_t)(uint64 >> 32) >= 0 && (int32_t)uint64 >= 0);\n    return (ui_edit_pg_t){ .pn = (int32_t)(uint64 >> 32), .gp = (int32_t)uint64 };\n}\n\nstatic bool ui_edit_range_inside_text(const ui_edit_text_t* t,\n        const ui_edit_range_t r) {\n    return ui_edit_range.is_valid(r) &&\n            0 <= r.from.pn && r.from.pn <= r.to.pn && r.to.pn < t->np &&\n            0 <= r.from.gp && r.from.gp <= r.to.gp &&\n            r.to.gp <= t->ps[r.to.pn - 1].g;\n}\n\nstatic ui_edit_range_t ui_edit_range_intersect(const ui_edit_range_t r1,\n    const ui_edit_range_t r2) {\n    if (ui_edit_range.is_valid(r1) && ui_edit_range.is_valid(r2)) {\n        ui_edit_range_t o1 = ui_edit_range.order(r1);\n        ui_edit_range_t o2 = ui_edit_range.order(r1);\n        uint64_t f1 = ((uint64_t)o1.from.pn << 32) | o1.from.gp;\n        uint64_t t1 = ((uint64_t)o1.to.pn   << 32) | o1.to.gp;\n        uint64_t f2 = ((uint64_t)o2.from.pn << 32) | o2.from.gp;\n        uint64_t t2 = ((uint64_t)o2.to.pn   << 32) | o2.to.gp;\n        if (f1 <= f2 && f2 <= t1) { // f2 is inside r1\n            if (t2 <= t1) { // r2 is fully inside r1\n                return r2;\n            } else { // r2 is partially inside r1\n                ui_edit_range_t r = {0};\n                r.from.pn = (int32_t)(f2 >> 32);\n                r.from.gp = (int32_t)(f2);\n                r.to.pn   = (int32_t)(t1 >> 32);\n                r.to.gp   = (int32_t)(t1);\n                return r;\n            }\n        } else if (f2 <= f1 && f1 <= t2) { // f1 is inside r2\n            if (t1 <= t2) { // r1 is fully inside r2\n                return r1;\n            } else { // r1 is partially inside r2\n                ui_edit_range_t r = {0};\n                r.from.pn = (int32_t)(f1 >> 32);\n                r.from.gp = (int32_t)(f1);\n                r.to.pn   = (int32_t)(t2 >> 32);\n                r.to.gp   = (int32_t)(t2);\n                return r;\n            }\n        } else {\n            return *ui_edit_range.invalid_range;\n        }\n    } else {\n        return *ui_edit_range.invalid_range;\n    }\n}\n\nstatic bool ui_edit_doc_realloc_ps_no_init(ui_edit_str_t* *ps,\n        int32_t old_np, int32_t new_np) { // reallocate paragraphs\n    for (int32_t i = new_np; i < old_np; i++) { ui_edit_str.free(&(*ps)[i]); }\n    bool ok = true;\n    if (new_np == 0) {\n        rt_heap.free(*ps);\n        *ps = null;\n    } else {\n        ok = rt_heap.realloc_zero((void**)ps, new_np * sizeof(ui_edit_str_t)) == 0;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_realloc_ps(ui_edit_str_t* *ps,\n        int32_t old_np, int32_t new_np) { // reallocate paragraphs\n    bool ok = ui_edit_doc_realloc_ps_no_init(ps, old_np, new_np);\n    if (ok) {\n        for (int32_t i = old_np; i < new_np; i++) {\n            ok = ui_edit_str.init(&(*ps)[i], null, 0, false);\n            rt_swear(ok, \"because .init(\\\"\\\", 0) does NOT allocate memory\");\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_init(ui_edit_text_t* t,\n        const char* s, int32_t b, bool heap) {\n    // When text comes from the source that lifetime is shorter\n    // than text itself (e.g. paste from clipboard) the parameter\n    // heap: true allows to make a copy of data on the heap\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    if (b < 0) { b = (int32_t)strlen(s); }\n    // if caller is concerned with best performance - it should pass b >= 0\n    int32_t np = 0; // number of paragraphs\n    int32_t n = rt_max(b / 64, 2); // initial number of allocated paragraphs\n    ui_edit_str_t* ps = null; // ps[n]\n    bool ok = ui_edit_doc_realloc_ps(&ps, 0, n);\n    if (ok) {\n        bool lf = false;\n        int32_t i = 0;\n        while (ok && i < b) {\n            int32_t k = i;\n            while (k < b && s[k] != '\\n') { k++; }\n            lf = k < b && s[k] == '\\n';\n            if (np >= n) {\n                int32_t n1_5 = n * 3 / 2; // n * 1.5\n                rt_assert(n1_5 > n);\n                ok = ui_edit_doc_realloc_ps(&ps, n, n1_5);\n                if (ok) { n = n1_5; }\n            }\n            if (ok) {\n                // insider knowledge about ui_edit_str allocation behaviour:\n                rt_assert(ps[np].c == 0 && ps[np].b == 0 &&\n                       ps[np].g2b[0] == 0);\n                ui_edit_str.free(&ps[np]);\n                // process \"\\r\\n\" strings\n                const int32_t e = k > i && s[k - 1] == '\\r' ? k - 1 : k;\n                const int32_t bytes = e - i; rt_assert(bytes >= 0);\n                const char* u = bytes == 0 ? null : s + i;\n                // str.init may allocate str.g2b[] on the heap and may fail\n                ok = ui_edit_str.init(&ps[np], u, bytes, heap && bytes > 0);\n                if (ok) { np++; }\n            }\n            i = k + lf;\n        }\n        if (ok && lf) { // last paragraph ended with line feed\n            if (np + 1 >= n) {\n                ok = ui_edit_doc_realloc_ps(&ps, n, n + 1);\n                if (ok) { n = n + 1; }\n            }\n            if (ok) { np++; }\n        }\n    }\n    if (ok && np == 0) { // special case empty string to a single paragraph\n        rt_assert(b <= 0 && (b == 0 || s[0] == 0x00));\n        np = 1; // ps[0] is already initialized as empty str\n        ok = ui_edit_doc_realloc_ps(&ps, n, 1);\n        rt_swear(ok, \"shrinking ps[] above\");\n    }\n    if (ok) {\n        rt_assert(np > 0);\n        t->np = np;\n        t->ps = ps;\n    } else if (ps != null) {\n        bool shrink = ui_edit_doc_realloc_ps(&ps, n, 0); // free()\n        rt_swear(shrink);\n        rt_heap.free(ps);\n        t->np = 0;\n        t->ps = null;\n    }\n    return ok;\n}\n\nstatic void ui_edit_text_dispose(ui_edit_text_t* t) {\n    if (t->np != 0) {\n        ui_edit_doc_realloc_ps(&t->ps, t->np, 0);\n        rt_assert(t->ps == null);\n        t->np = 0;\n    } else {\n        rt_assert(t->np == 0 && t->ps == null);\n    }\n}\n\nstatic void ui_edit_doc_dispose_to_do(ui_edit_to_do_t* to_do) {\n    if (to_do->text.np > 0) {\n        ui_edit_text_dispose(&to_do->text);\n    }\n    memset(&to_do->range, 0x00, sizeof(to_do->range));\n    ui_edit_check_zeros(to_do, sizeof(*to_do));\n}\n\nstatic int32_t ui_edit_text_bytes(const ui_edit_text_t* t,\n        const ui_edit_range_t* range) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    int32_t bytes = 0;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes += p->g2b[r.to.gp] - p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes += p->b - p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes += p->g2b[r.to.gp];\n        } else {\n            bytes += p->b;\n        }\n    }\n    return bytes;\n}\n\nstatic int32_t ui_edit_doc_bytes(const ui_edit_doc_t* d,\n        const ui_edit_range_t* r) {\n    return ui_edit_text.bytes(&d->text, r);\n}\n\nstatic int32_t ui_edit_doc_utf8bytes(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range) {\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    int32_t bytes = ui_edit_text.bytes(&d->text, &r);\n    // \"\\n\" after each paragraph and 0x00\n    return bytes + r.to.pn - r.from.pn + 1;\n}\n\nstatic void ui_edit_notify_before(ui_edit_doc_t* d,\n        const ui_edit_notify_info_t* ni) {\n    ui_edit_listener_t* o = d->listeners;\n    while (o != null) {\n        if (o->notify != null && o->notify->before != null) {\n            o->notify->before(o->notify, ni);\n        }\n        o = o->next;\n    }\n}\n\nstatic void ui_edit_notify_after(ui_edit_doc_t* d,\n        const ui_edit_notify_info_t* ni) {\n    ui_edit_listener_t* o = d->listeners;\n    while (o != null) {\n        if (o->notify != null && o->notify->after != null) {\n            o->notify->after(o->notify, ni);\n        }\n        o = o->next;\n    }\n}\n\nstatic bool ui_edit_doc_subscribe(ui_edit_doc_t* t, ui_edit_notify_t* notify) {\n    // TODO: not sure about double linked list.\n    // heap allocated resizable array may serve better and may be easier to maintain\n    bool ok = true;\n    ui_edit_listener_t* o = t->listeners;\n    if (o == null) {\n        ok = rt_heap.alloc_zero((void**)&t->listeners, sizeof(*o)) == 0;\n        if (ok) { o = t->listeners; }\n    } else {\n        while (o->next != null) { rt_swear(o->notify != notify); o = o->next; }\n        ok = rt_heap.alloc_zero((void**)&o->next, sizeof(*o)) == 0;\n        if (ok) { o->next->prev = o; o = o->next; }\n    }\n    if (ok) { o->notify = notify; }\n    return ok;\n}\n\nstatic void ui_edit_doc_unsubscribe(ui_edit_doc_t* t, ui_edit_notify_t* notify) {\n    ui_edit_listener_t* o = t->listeners;\n    bool removed = false;\n    while (o != null) {\n        ui_edit_listener_t* n = o->next;\n        if (o->notify == notify) {\n            rt_assert(!removed);\n            if (o->prev != null) { o->prev->next = n; }\n            if (o->next != null) { o->next->prev = o->prev; }\n            if (o == t->listeners) { t->listeners = n; }\n            rt_heap.free(o);\n            removed = true;\n        }\n        o = n;\n    }\n    rt_swear(removed);\n}\n\nstatic bool ui_edit_doc_copy_text(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range, ui_edit_text_t* t) {\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    ui_edit_check_range_inside_text(&d->text, &r);\n    int32_t np = r.to.pn - r.from.pn + 1;\n    bool ok = ui_edit_doc_realloc_ps(&t->ps, 0, np);\n    if (ok) { t->np = np; }\n    for (int32_t pn = r.from.pn; ok && pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &d->text.ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        rt_assert(t->ps[pn - r.from.pn].g == 0);\n        const char* u_or_null = bytes == 0 ? null : u;\n        ui_edit_str.replace(&t->ps[pn - r.from.pn], 0, 0, u_or_null, bytes);\n    }\n    if (!ok) {\n        ui_edit_text.dispose(t);\n        ui_edit_check_zeros(t, sizeof(*t));\n    }\n    return ok;\n}\n\nstatic void ui_edit_doc_copy(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range, char* text, int32_t b) {\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    ui_edit_check_range_inside_text(&d->text, &r);\n    char* to = text;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &d->text.ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        const int32_t c = (int32_t)(uintptr_t)(to - text);\n        if (bytes > 0) {\n            rt_swear(c + bytes < b, \"c: %d bytes: %d b: %d\", c, bytes, b);\n            memmove(to, u, (size_t)bytes);\n            to += bytes;\n        }\n        if (pn < r.to.pn) {\n            rt_swear(c + bytes < b, \"c: %d bytes: %d b: %d\", c, bytes, b);\n            *to++ = '\\n';\n        }\n    }\n    const int32_t c = (int32_t)(uintptr_t)(to - text);\n    rt_swear(c + 1 == b, \"c: %d b: %d\", c, b);\n    *to++ = 0x00;\n}\n\nstatic bool ui_edit_text_insert_2_or_more(ui_edit_text_t* t, int32_t pn,\n        const ui_edit_str_t* s, const ui_edit_text_t* insert,\n        const ui_edit_str_t* e) {\n    // insert 2 or more paragraphs\n    rt_assert(0 <= pn && pn < t->np);\n    const int32_t np = t->np + insert->np - 1;\n    rt_assert(np > 0);\n    ui_edit_str_t* ps = null; // ps[np]\n    bool ok = ui_edit_doc_realloc_ps_no_init(&ps, 0, np);\n    if (ok) {\n        memmove(ps, t->ps, (size_t)pn * sizeof(ui_edit_str_t));\n        // `s` first line of `insert`\n        ok = ui_edit_str.init(&ps[pn], s->u, s->b, true);\n        // lines of `insert` between `s` and `e`\n        for (int32_t i = 1; ok && i < insert->np - 1; i++) {\n            ok = ui_edit_str.init(&ps[pn + i], insert->ps[i].u,\n                                               insert->ps[i].b, true);\n        }\n        // `e` last line of `insert`\n        if (ok) {\n            const int32_t ix = pn + insert->np - 1; // last `insert` index\n            ok = ui_edit_str.init(&ps[ix], e->u, e->b, true);\n        }\n        rt_assert(t->np - pn - 1 >= 0);\n        memmove(ps + pn + insert->np, t->ps + pn + 1,\n               (size_t)(t->np - pn - 1) * sizeof(ui_edit_str_t));\n        if (ok) {\n            // this two regions where moved to `ps`\n            memset(t->ps, 0x00, pn * sizeof(ui_edit_str_t));\n            memset(t->ps + pn + 1, 0x00,\n                   (size_t)(t->np - pn - 1) * sizeof(ui_edit_str_t));\n            // deallocate what was copied from `insert`\n            ui_edit_doc_realloc_ps_no_init(&t->ps, t->np, 0);\n            t->np = np;\n            t->ps = ps;\n        } else { // free allocated memory:\n            ui_edit_doc_realloc_ps_no_init(&ps, np, 0);\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert_1(ui_edit_text_t* t,\n        const ui_edit_pg_t ip, // insertion point\n        const ui_edit_text_t* insert) {\n    rt_assert(0 <= ip.pn && ip.pn < t->np);\n    ui_edit_str_t* str = &t->ps[ip.pn]; // string in document text\n    rt_assert(insert->np == 1);\n    ui_edit_str_t* ins = &insert->ps[0]; // string to insert\n    rt_assert(0 <= ip.gp && ip.gp <= str->g);\n    // ui_edit_str.replace() is all or nothing:\n    return ui_edit_str.replace(str, ip.gp, ip.gp, ins->u, ins->b);\n}\n\nstatic bool ui_edit_substr_append(ui_edit_str_t* d, const ui_edit_str_t* s1,\n    int32_t gp1, const ui_edit_str_t* s2) { // s1[0:gp1] + s2\n    rt_assert(d != s1 && d != s2);\n    const int32_t b = s1->g2b[gp1];\n    bool ok = ui_edit_str.init(d, b == 0 ? null : s1->u, b, true);\n    if (ok) {\n        ok = ui_edit_str.replace(d, d->g, d->g, s2->u, s2->b);\n    } else {\n        *d = *ui_edit_str.empty;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_append_substr(ui_edit_str_t* d, const ui_edit_str_t* s1,\n    const ui_edit_str_t* s2, int32_t gp2) {  // s1 + s2[gp1:*]\n    rt_assert(d != s1 && d != s2);\n    bool ok = ui_edit_str.init(d, s1->b == 0 ? null : s1->u, s1->b, true);\n    if (ok) {\n        const int32_t o = s2->g2b[gp2]; // offset (bytes)\n        const int32_t b = s2->b - o;\n        ok = ui_edit_str.replace(d, d->g, d->g, b == 0 ? null : s2->u + o, b);\n    } else {\n        *d = *ui_edit_str.empty;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert(ui_edit_text_t* t, const ui_edit_pg_t ip,\n        const ui_edit_text_t* i) {\n    bool ok = true;\n    if (ok) {\n        if (i->np == 1) {\n            ok = ui_edit_text_insert_1(t, ip, i);\n        } else {\n            ui_edit_str_t* str = &t->ps[ip.pn];\n            ui_edit_str_t s = {0}; // start line of insert text `i`\n            ui_edit_str_t e = {0}; // end   line\n            if (ui_edit_substr_append(&s, str, ip.gp, &i->ps[0])) {\n                if (ui_edit_append_substr(&e, &i->ps[i->np - 1], str, ip.gp)) {\n                    ok = ui_edit_text_insert_2_or_more(t, ip.pn, &s, i, &e);\n                    ui_edit_str.free(&e);\n                }\n                ui_edit_str.free(&s);\n            }\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_remove_lines(ui_edit_text_t* t,\n    ui_edit_str_t* merge, int32_t from, int32_t to) {\n    bool ok = true;\n    for (int32_t pn = from + 1; pn <= to; pn++) {\n        ui_edit_str.free(&t->ps[pn]);\n    }\n    if (t->np - to - 1 > 0) {\n        memmove(&t->ps[from + 1], &t->ps[to + 1],\n                (size_t)(t->np - to - 1) * sizeof(ui_edit_str_t));\n    }\n    t->np -= to - from;\n    if (ok) {\n        ui_edit_str.swap(&t->ps[from], merge);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert_remove(ui_edit_text_t* t,\n        const ui_edit_range_t r, const ui_edit_text_t* i) {\n    bool ok = true;\n    ui_edit_str_t merge = {0};\n    const ui_edit_str_t* s = &t->ps[r.from.pn];\n    const ui_edit_str_t* e = &t->ps[r.to.pn];\n    const int32_t o = e->g2b[r.to.gp];\n    const int32_t b = e->b - o;\n    const char* u = b == 0 ? null : e->u + o;\n    ok = ui_edit_substr_append(&merge, s, r.from.gp, &i->ps[i->np - 1]) &&\n         ui_edit_str.replace(&merge, merge.g, merge.g, u, b);\n    if (ok) {\n        const bool empty_text = i->np == 1 && i->ps[0].g == 0;\n        if (!empty_text) {\n            ok = ui_edit_text_insert(t, r.to, i);\n        }\n        if (ok) {\n            ok = ui_edit_text_remove_lines(t, &merge, r.from.pn, r.to.pn);\n        }\n    }\n    if (merge.c > 0 || merge.g > 0) { ui_edit_str.free(&merge); }\n    return ok;\n}\n\nstatic bool ui_edit_text_copy_text(const ui_edit_text_t* t,\n        const ui_edit_range_t* range, ui_edit_text_t* to) {\n    ui_edit_check_zeros(to, sizeof(*to));\n    memset(to, 0x00, sizeof(*to));\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    int32_t np = r.to.pn - r.from.pn + 1;\n    bool ok = ui_edit_doc_realloc_ps(&to->ps, 0, np);\n    if (ok) { to->np = np; }\n    for (int32_t pn = r.from.pn; ok && pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        rt_assert(to->ps[pn - r.from.pn].g == 0);\n        const char* u_or_null = bytes == 0 ? null : u;\n        ui_edit_str.replace(&to->ps[pn - r.from.pn], 0, 0, u_or_null, bytes);\n    }\n    if (!ok) {\n        ui_edit_text.dispose(to);\n        ui_edit_check_zeros(to, sizeof(*to));\n    }\n    return ok;\n}\n\nstatic void ui_edit_text_copy(const ui_edit_text_t* t,\n        const ui_edit_range_t* range, char* text, int32_t b) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    char* to = text;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        const int32_t c = (int32_t)(uintptr_t)(to - text);\n        rt_swear(c + bytes < b, \"d: %d bytes:%d b: %d\", c, bytes, b);\n        if (bytes > 0) {\n            memmove(to, u, (size_t)bytes);\n            to += bytes;\n        }\n        if (pn < r.to.pn) {\n            rt_swear(c + bytes + 1 < b, \"d: %d bytes:%d b: %d\", c, bytes, b);\n            *to++ = '\\n';\n        }\n    }\n    const int32_t c = (int32_t)(uintptr_t)(to - text);\n    rt_swear(c + 1 == b, \"d: %d b: %d\", c, b);\n    *to++ = 0x00;\n}\n\nstatic bool ui_edit_text_replace(ui_edit_text_t* t,\n        const ui_edit_range_t* range, const ui_edit_text_t* i,\n        ui_edit_to_do_t* undo) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    bool ok = undo == null ? true : ui_edit_text.copy_text(t, &r, &undo->text);\n    ui_edit_range_t x = r;\n    if (ok) {\n        if (ui_edit_range.is_empty(r)) {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = i->np == 1 ? r.from.gp + i->ps[0].g : i->ps[i->np - 1].g;\n            ok = ui_edit_text_insert(t, r.from, i);\n        } else if (i->np == 1 && r.from.pn == r.to.pn) {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = r.from.gp + i->ps[0].g;\n            ok = ui_edit_str.replace(&t->ps[r.from.pn],\n                    r.from.gp, r.to.gp, i->ps[0].u, i->ps[0].b);\n        } else {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = i->np == 1 ? r.from.gp + i->ps[0].g : i->ps[0].g;\n            ok = ui_edit_text_insert_remove(t, r, i);\n        }\n    }\n    if (undo != null) { undo->range = x; }\n    return ok;\n}\n\nstatic bool ui_edit_text_replace_utf8(ui_edit_text_t* t,\n        const ui_edit_range_t* range,\n        const char* utf8, int32_t b,\n        ui_edit_to_do_t* undo) {\n    if (b < 0) { b = (int32_t)strlen(utf8); }\n    ui_edit_text_t i = {0};\n    bool ok = ui_edit_text.init(&i, utf8, b, false);\n    if (ok) {\n        ok = ui_edit_text.replace(t, range, &i, undo);\n        ui_edit_text.dispose(&i);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_dup(ui_edit_text_t* t, const ui_edit_text_t* s) {\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    bool ok = ui_edit_doc_realloc_ps(&t->ps, 0, s->np);\n    if (ok) {\n        t->np = s->np;\n        for (int32_t i = 0; ok && i < s->np; i++) {\n            const ui_edit_str_t* p = &s->ps[i];\n            ok = ui_edit_str.replace(&t->ps[i], 0, 0, p->u, p->b);\n        }\n    }\n    if (!ok) {\n        ui_edit_text.dispose(t);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_equal(const ui_edit_text_t* t1,\n        const ui_edit_text_t* t2) {\n    bool equal =  t1->np != t2->np;\n    for (int32_t i = 0; equal && i < t1->np; i++) {\n        const ui_edit_str_t* p1 = &t1->ps[i];\n        const ui_edit_str_t* p2 = &t2->ps[i];\n        equal = p1->b == p2->b &&\n                memcmp(p1->u, p2->u, p1->b) == 0;\n    }\n    return equal;\n}\n\nstatic void ui_edit_doc_before_replace_text(ui_edit_doc_t* d,\n        const ui_edit_range_t r, const ui_edit_text_t* t) {\n    ui_edit_check_range_inside_text(&d->text, &r);\n    ui_edit_range_t x = r;\n    x.to.pn = r.from.pn + t->np - 1;\n    if (r.from.pn == r.to.pn && t->np == 1) {\n        x.to.gp = r.from.gp + t->ps[0].g;\n    } else {\n        x.to.gp = t->ps[t->np - 1].g;\n    }\n    const ui_edit_notify_info_t ni_before = {\n        .ok = true, .d = d, .r = &r, .x = &x, .t = t,\n        .pnf = r.from.pn, .pnt = r.to.pn,\n        .deleted = 0, .inserted = 0\n    };\n    ui_edit_notify_before(d, &ni_before);\n}\n\nstatic void ui_edit_doc_after_replace_text(ui_edit_doc_t* d,\n        bool ok,\n        const ui_edit_range_t r,\n        const ui_edit_range_t x,\n        const ui_edit_text_t* t) {\n    const ui_edit_notify_info_t ni_after = {\n        .ok = ok, .d = d, .r = &r, .x = &x, .t = t,\n        .pnf = r.from.pn, .pnt = x.to.pn,\n        .deleted = r.to.pn - r.from.pn,\n        .inserted = t->np - 1\n    };\n    ui_edit_notify_after(d, &ni_after);\n}\n\nstatic bool ui_edit_doc_replace_text(ui_edit_doc_t* d,\n        const ui_edit_range_t* range, const ui_edit_text_t* i,\n        ui_edit_to_do_t* undo) {\n    ui_edit_text_t* t = &d->text;\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_doc_before_replace_text(d, r, i);\n    bool ok = ui_edit_text.replace(t, &r, i, undo);\n    ui_edit_doc_after_replace_text(d, ok, r, undo->range, i);\n    return ok;\n}\n\nstatic bool ui_edit_doc_replace_undoable(ui_edit_doc_t* d,\n        const ui_edit_range_t* r, const ui_edit_text_t* t,\n        ui_edit_to_do_t* undo) {\n    bool ok = ui_edit_doc_replace_text(d, r, t, undo);\n    if (ok && undo != null) {\n        undo->next = d->undo;\n        d->undo = undo;\n        // redo stack is not valid after new replace, empty it:\n        while (d->redo != null) {\n            ui_edit_to_do_t* next = d->redo->next;\n            d->redo->next = null;\n            ui_edit_doc.dispose_to_do(d->redo);\n            rt_heap.free(d->redo);\n            d->redo = next;\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_utf8_to_heap_text(const char* u, int32_t b,\n        ui_edit_text_t* it) {\n    rt_assert((b == 0) == (u == null || u[0] == 0x00));\n    return ui_edit_text.init(it, b != 0 ? u : null, b, true);\n}\n\n\nstatic bool ui_edit_doc_coalesce_undo(ui_edit_doc_t* d, ui_edit_text_t* i) {\n    ui_edit_to_do_t* undo = d->undo;\n    ui_edit_to_do_t* next = undo->next;\n//  rt_println(\"i: %.*s\", i->ps[0].b, i->ps[0].u);\n//  if (i->np == 1 && i->ps[0].g == 1) {\n//      rt_println(\"an: %d\", ui_edit_str.is_letter(rt_str.utf32(i->ps[0].u, i->ps[0].b)));\n//  }\n    bool coalesced = false;\n    const bool alpha_numeric = i->np == 1 && i->ps[0].g == 1 &&\n        ui_edit_str.is_letter(rt_str.utf32(i->ps[0].u, i->ps[0].b));\n    if (alpha_numeric && next != null) {\n        const ui_edit_range_t ur = undo->range;\n        const ui_edit_text_t* ut = &undo->text;\n        const ui_edit_range_t nr = next->range;\n        const ui_edit_text_t* nt = &next->text;\n//      rt_println(\"next: \\\"%.*s\\\" %d:%d..%d:%d undo: \\\"%.*s\\\" %d:%d..%d:%d\",\n//          nt->ps[0].b, nt->ps[0].u, nr.from.pn, nr.from.gp, nr.to.pn, nr.to.gp,\n//          ut->ps[0].b, ut->ps[0].u, ur.from.pn, ur.from.gp, ur.to.pn, ur.to.gp);\n        const bool c =\n            nr.from.pn == nr.to.pn && ur.from.pn == ur.to.pn &&\n            nr.from.pn == ur.from.pn &&\n            ut->np == 1 && ut->ps[0].g == 0 &&\n            nt->np == 1 && nt->ps[0].g == 0 &&\n            nr.to.gp == ur.from.gp && nr.to.gp > 0;\n        if (c) {\n            const ui_edit_str_t* str = &d->text.ps[nr.from.pn];\n            const int32_t* g2b = str->g2b;\n            const char* utf8 = str->u + g2b[nr.to.gp - 1];\n            uint32_t utf32 = rt_str.utf32(utf8, g2b[nr.to.gp] - g2b[nr.to.gp - 1]);\n            coalesced = ui_edit_str.is_letter(utf32);\n        }\n        if (coalesced) {\n//          rt_println(\"coalesced\");\n            next->range.to.gp++;\n            d->undo = next;\n            undo->next = null;\n            coalesced = true;\n        }\n    }\n    return coalesced;\n}\n\nstatic bool ui_edit_doc_replace(ui_edit_doc_t* d,\n        const ui_edit_range_t* range, const char* u, int32_t b) {\n    ui_edit_text_t* t = &d->text;\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_to_do_t* undo = null;\n    bool ok = rt_heap.alloc_zero((void**)&undo, sizeof(ui_edit_to_do_t)) == 0;\n    if (ok) {\n        ui_edit_text_t i = {0};\n        ok = ui_edit_utf8_to_heap_text(u, b, &i);\n        if (ok) {\n            ok = ui_edit_doc_replace_undoable(d, &r, &i, undo);\n            if (ok) {\n                if (ui_edit_doc_coalesce_undo(d, &i)) {\n                    ui_edit_doc.dispose_to_do(undo);\n                    rt_heap.free(undo);\n                    undo = null;\n                }\n            }\n            ui_edit_text.dispose(&i);\n        }\n        if (!ok) {\n            ui_edit_doc.dispose_to_do(undo);\n            rt_heap.free(undo);\n            undo = null;\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_do(ui_edit_doc_t* d, ui_edit_to_do_t* to_do,\n        ui_edit_to_do_t* *stack) {\n    const ui_edit_range_t* r = &to_do->range;\n    ui_edit_to_do_t* redo = null;\n    bool ok = rt_heap.alloc_zero((void**)&redo, sizeof(ui_edit_to_do_t)) == 0;\n    if (ok) {\n        ok = ui_edit_doc_replace_text(d, r, &to_do->text, redo);\n        if (ok) {\n            ui_edit_doc.dispose_to_do(to_do);\n            rt_heap.free(to_do);\n        }\n        if (ok) {\n            redo->next = *stack;\n            *stack = redo;\n        } else {\n            if (redo != null) {\n                ui_edit_doc.dispose_to_do(redo);\n                rt_heap.free(redo);\n            }\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_redo(ui_edit_doc_t* d) {\n    ui_edit_to_do_t* to_do = d->redo;\n    if (to_do == null) {\n        return false;\n    } else {\n        d->redo = d->redo->next;\n        to_do->next = null;\n        return ui_edit_doc_do(d, to_do, &d->undo);\n    }\n}\n\nstatic bool ui_edit_doc_undo(ui_edit_doc_t* d) {\n    ui_edit_to_do_t* to_do = d->undo;\n    if (to_do == null) {\n        return false;\n    } else {\n        d->undo = d->undo->next;\n        to_do->next = null;\n        return ui_edit_doc_do(d, to_do, &d->redo);\n    }\n}\n\nstatic bool ui_edit_doc_init(ui_edit_doc_t* d, const char* utf8,\n        int32_t bytes, bool heap) {\n    bool ok = true;\n    ui_edit_check_zeros(d, sizeof(*d));\n    memset(d, 0x00, sizeof(d));\n    if (bytes < 0) {\n        size_t n = strlen(utf8);\n        rt_swear(n < INT32_MAX);\n        bytes = (int32_t)n;\n    }\n    rt_assert((utf8 == null) == (bytes == 0));\n    if (ok) {\n        if (bytes == 0) { // empty string\n            ok = rt_heap.alloc_zero((void**)&d->text.ps, sizeof(ui_edit_str_t)) == 0;\n            if (ok) {\n                d->text.np = 1;\n                ok = ui_edit_str.init(&d->text.ps[0], null, 0, false);\n            }\n        } else {\n            ok = ui_edit_text.init(&d->text, utf8, bytes, heap);\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_doc_dispose(ui_edit_doc_t* d) {\n    for (int32_t i = 0; i < d->text.np; i++) {\n        ui_edit_str.free(&d->text.ps[i]);\n    }\n    if (d->text.ps != null) {\n        rt_heap.free(d->text.ps);\n        d->text.ps = null;\n    }\n    d->text.np  = 0;\n    while (d->undo != null) {\n        ui_edit_to_do_t* next = d->undo->next;\n        d->undo->next = null;\n        ui_edit_doc.dispose_to_do(d->undo);\n        rt_heap.free(d->undo);\n        d->undo = next;\n    }\n    while (d->redo != null) {\n        ui_edit_to_do_t* next = d->redo->next;\n        d->redo->next = null;\n        ui_edit_doc.dispose_to_do(d->redo);\n        rt_heap.free(d->redo);\n        d->redo = next;\n    }\n    rt_assert(d->listeners == null, \"unsubscribe listeners?\");\n    while (d->listeners != null) {\n        ui_edit_listener_t* next = d->listeners->next;\n        d->listeners->next = null;\n        rt_heap.free(d->listeners->next);\n        d->listeners = next;\n    }\n    ui_edit_check_zeros(d, sizeof(*d));\n}\n\n// ui_edit_str\n\nstatic int32_t ui_edit_str_g2b_ascii[1024]; // ui_edit_str_g2b_ascii[i] == i for all \"i\"\nstatic char    ui_edit_str_empty_utf8[1] = {0x00};\n\nstatic const ui_edit_str_t ui_edit_str_empty = {\n    .u = ui_edit_str_empty_utf8,\n    .g2b = ui_edit_str_g2b_ascii,\n    .c = 0, .b = 0, .g = 0\n};\n\nstatic bool    ui_edit_str_init(ui_edit_str_t* s, const char* u, int32_t b, bool heap);\nstatic void    ui_edit_str_swap(ui_edit_str_t* s1, ui_edit_str_t* s2);\nstatic int32_t ui_edit_str_gp_to_bp(const char* s, int32_t bytes, int32_t gp);\nstatic int32_t ui_edit_str_bytes(ui_edit_str_t* s, int32_t f, int32_t t);\nstatic bool    ui_edit_str_expand(ui_edit_str_t* s, int32_t c);\nstatic void    ui_edit_str_shrink(ui_edit_str_t* s);\nstatic bool    ui_edit_str_replace(ui_edit_str_t* s, int32_t f, int32_t t,\n                                   const char* u, int32_t b);\n\n//  bool (*is_zwj)(uint32_t utf32); // zero width joiner\n//  bool (*is_letter)(uint32_t utf32); // in European Alphabets\n//  bool (*is_digit)(uint32_t utf32);\n//  bool (*is_symbol)(uint32_t utf32);\n//  bool (*is_alphanumeric)(uint32_t utf32);\n//  bool (*is_blank)(uint32_t utf32); // white space\n//  bool (*is_punctuation)(uint32_t utf32);\n//  bool (*is_combining)(uint32_t utf32);\n//  bool (*is_spacing)(uint32_t utf32); // spacing modifiers\n//  bool (*is_cjk_or_emoji)(uint32_t utf32);\n\nstatic bool ui_edit_str_is_zwj(uint32_t utf32);\nstatic bool ui_edit_str_is_letter(uint32_t utf32);\nstatic bool ui_edit_str_is_digit(uint32_t utf32);\nstatic bool ui_edit_str_is_symbol(uint32_t utf32);\nstatic bool ui_edit_str_is_alphanumeric(uint32_t utf32);\nstatic bool ui_edit_str_is_blank(uint32_t utf32);\nstatic bool ui_edit_str_is_punctuation(uint32_t utf32);\nstatic bool ui_edit_str_is_combining(uint32_t utf32);\nstatic bool ui_edit_str_is_spacing(uint32_t utf32);\nstatic bool ui_edit_str_is_blank(uint32_t utf32);\nstatic bool ui_edit_str_is_cjk_or_emoji(uint32_t utf32);\nstatic bool ui_edit_str_can_break(uint32_t cp1, uint32_t cp2);\n\nstatic void    ui_edit_str_test(void);\nstatic void    ui_edit_str_free(ui_edit_str_t* s);\n\nui_edit_str_if ui_edit_str = {\n    .init            = ui_edit_str_init,\n    .swap            = ui_edit_str_swap,\n    .gp_to_bp        = ui_edit_str_gp_to_bp,\n    .bytes           = ui_edit_str_bytes,\n    .expand          = ui_edit_str_expand,\n    .shrink          = ui_edit_str_shrink,\n    .replace         = ui_edit_str_replace,\n    .is_zwj          = ui_edit_str_is_zwj,\n    .is_letter       = ui_edit_str_is_letter,\n    .is_digit        = ui_edit_str_is_digit,\n    .is_symbol       = ui_edit_str_is_symbol,\n    .is_alphanumeric = ui_edit_str_is_alphanumeric,\n    .is_blank        = ui_edit_str_is_blank,\n    .is_punctuation  = ui_edit_str_is_punctuation,\n    .is_combining    = ui_edit_str_is_combining,\n    .is_spacing      = ui_edit_str_is_spacing,\n    .is_punctuation  = ui_edit_str_is_punctuation,\n    .is_cjk_or_emoji = ui_edit_str_is_cjk_or_emoji,\n    .can_break       = ui_edit_str_can_break,\n    .test            = ui_edit_str_test,\n    .free            = ui_edit_str_free,\n    .empty           = &ui_edit_str_empty\n};\n\n#pragma push_macro(\"ui_edit_str_check\")\n#pragma push_macro(\"ui_edit_str_check_from_to\")\n#pragma push_macro(\"ui_edit_check_zeros\")\n#pragma push_macro(\"ui_edit_str_check_empty\")\n#pragma push_macro(\"ui_edit_str_parameters\")\n\n#ifdef DEBUG\n\n#define ui_edit_str_check(s) do {                                   \\\n    /* check the s struct constrains */                             \\\n    rt_assert(s->b >= 0);                                              \\\n    rt_assert(s->c == 0 || s->c >= s->b);                              \\\n    rt_assert(s->g >= 0);                                              \\\n    /* s->g2b[] may be null (not heap allocated) when .b == 0 */    \\\n    if (s->g == 0) { rt_assert(s->b == 0); }                           \\\n    if (s->g > 0) {                                                 \\\n        rt_assert(s->g2b[0] == 0 && s->g2b[s->g] == s->b);             \\\n    }                                                               \\\n    for (int32_t i = 1; i < s->g; i++) {                            \\\n        rt_assert(0 < s->g2b[i] - s->g2b[i - 1] &&                     \\\n                   s->g2b[i] - s->g2b[i - 1] <= 4);                 \\\n        rt_assert(s->g2b[i] - s->g2b[i - 1] ==                         \\\n            rt_str.utf8bytes(                                 \\\n            s->u + s->g2b[i - 1], s->g2b[i] - s->g2b[i - 1]));      \\\n    }                                                               \\\n} while (0)\n\n#define ui_edit_str_check_from_to(s, f, t) do {                     \\\n    rt_assert(0 <= f && f <= s->g);                                    \\\n    rt_assert(0 <= t && t <= s->g);                                    \\\n    rt_assert(f <= t);                                                 \\\n} while (0)\n\n#define ui_edit_str_check_empty(u, b) do {                          \\\n    if (b == 0) { rt_assert(u != null && u[0] == 0x00); }              \\\n    if (u == null || u[0] == 0x00) { rt_assert(b == 0); }              \\\n} while (0)\n\n\n\n#else\n\n#define ui_edit_str_check(s)               do { } while (0)\n#define ui_edit_str_check_from_to(s, f, t) do { } while (0)\n#define ui_edit_str_check_empty(u, b)      do { } while (0)\n\n#endif\n\n// ui_edit_str_foo(*, \"...\", -1) treat as 0x00 terminated\n// ui_edit_str_foo(*, null, 0) treat as (\"\", 0)\n\n#define ui_edit_str_parameters(u, b) do {                           \\\n    if (u == null) { u = ui_edit_str_empty_utf8; }                  \\\n    if (b < 0)  {                                                   \\\n        rt_assert(strlen(u) < INT32_MAX);                              \\\n        b = (int32_t)strlen(u);                                     \\\n    }                                                               \\\n    ui_edit_str_check_empty(u, b);                                  \\\n} while (0)\n\nstatic int32_t ui_edit_str_gp_to_bp(const char* utf8, int32_t bytes, int32_t gp) {\n    rt_swear(bytes >= 0);\n    bool ok = true;\n    int32_t c = 0;\n    int32_t i = 0;\n    if (bytes > 0) {\n        while (c < gp && ok) {\n            rt_assert(i < bytes);\n            const int32_t b = rt_str.utf8bytes(utf8 + i, bytes - i);\n            ok = 0 < b && i + b <= bytes;\n            if (ok) { i += b; c++; }\n        }\n    }\n    rt_assert(i <= bytes);\n    return ok ? i : -1;\n}\n\nstatic void ui_edit_str_free(ui_edit_str_t* s) {\n    if (s->g2b != null && s->g2b != ui_edit_str_g2b_ascii) {\n        rt_heap.free(s->g2b);\n    } else {\n        #ifdef UI_EDIT_STR_TEST // check ui_edit_str_g2b_ascii integrity\n            for (int32_t i = 0; i < rt_countof(ui_edit_str_g2b_ascii); i++) {\n                rt_assert(ui_edit_str_g2b_ascii[i] == i);\n            }\n        #endif\n    }\n    s->g2b = null;\n    s->g = 0;\n    if (s->c > 0) {\n        rt_heap.free(s->u);\n        s->u = null;\n        s->c = 0;\n        s->b = 0;\n    } else {\n        s->u = null;\n        s->b = 0;\n    }\n    ui_edit_check_zeros(s, sizeof(*s));\n}\n\nstatic bool ui_edit_str_init_g2b(ui_edit_str_t* s) {\n    const int64_t _4_bytes = (int64_t)sizeof(int32_t);\n    // start with number of glyphs == number of bytes (ASCII text):\n    bool ok = rt_heap.alloc(&s->g2b, (size_t)(s->b + 1) * _4_bytes) == 0;\n    int32_t i = 0; // index in u[] string\n    int32_t k = 1; // glyph number\n    // g2b[k] start postion in uint8_t offset from utf8 text of glyph[k]\n    while (i < s->b && ok) {\n        const int32_t b = rt_str.utf8bytes(s->u + i, s->b - i);\n        ok = b > 0 && i + b <= s->b;\n        if (ok) {\n            i += b;\n            s->g2b[k] = i;\n            k++;\n        }\n    }\n    if (ok) {\n        rt_assert(0 < k && k <= s->b + 1);\n        s->g2b[0] = 0;\n        rt_assert(s->g2b[k - 1] == s->b);\n        s->g = k - 1;\n        if (k < s->b + 1) {\n            ok = rt_heap.realloc(&s->g2b, k * _4_bytes) == 0;\n            rt_assert(ok, \"shrinking - should always be ok\");\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_str_init(ui_edit_str_t* s, const char* u, int32_t b,\n        bool heap) {\n    enum { n = rt_countof(ui_edit_str_g2b_ascii) };\n    if (ui_edit_str_g2b_ascii[n - 1] != n - 1) {\n        for (int32_t i = 0; i < n; i++) { ui_edit_str_g2b_ascii[i] = i; }\n    }\n    bool ok = true;\n    ui_edit_check_zeros(s, sizeof(*s)); // caller must zero out\n    memset(s, 0x00, sizeof(*s));\n    ui_edit_str_parameters(u, b);\n    if (b == 0) { // cast below intentionally removes \"const\" qualifier\n        s->g2b = (int32_t*)ui_edit_str_g2b_ascii;\n        s->u = (char*)u;\n        rt_assert(s->c == 0 && u[0] == 0x00);\n    } else {\n        if (heap) {\n            ok = rt_heap.alloc((void**)&s->u, b) == 0;\n            if (ok) { s->c = b; memmove(s->u, u, (size_t)b); }\n        } else {\n            s->u = (char*)u;\n        }\n        if (ok) {\n            s->b = b;\n            if (b == 1 && u[0] <= 0x7F) {\n                s->g2b = (int32_t*)ui_edit_str_g2b_ascii;\n                s->g = 1;\n            } else {\n                ok = ui_edit_str_init_g2b(s);\n            }\n        }\n    }\n    if (ok) { ui_edit_str.shrink(s); } else { ui_edit_str.free(s); }\n    return ok;\n}\n\nstatic void ui_edit_str_swap(ui_edit_str_t* s1, ui_edit_str_t* s2) {\n    ui_edit_str_t s = *s1; *s1 = *s2; *s2 = s;\n}\n\nstatic int32_t ui_edit_str_bytes(ui_edit_str_t* s,\n        int32_t f, int32_t t) { // glyph positions\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    return s->g2b[t] - s->g2b[f];\n}\n\nstatic bool ui_edit_str_move_g2b_to_heap(ui_edit_str_t* s) {\n    bool ok = true;\n    if (s->g2b == ui_edit_str_g2b_ascii) { // even for s->g == 0\n        if (s->b == s->g && s->g < rt_countof(ui_edit_str_g2b_ascii) - 1) {\n//          rt_println(\"forcefully moving to heap\");\n            // this is usually done in the process of concatenation\n            // of 2 ascii strings when result is known to be longer\n            // than rt_countof(ui_edit_str_g2b_ascii) - 1 but the\n            // first string in concatenation is short. It's OK.\n        }\n        const int32_t bytes = (s->g + 1) * (int32_t)sizeof(int32_t);\n        ok = rt_heap.alloc(&s->g2b, bytes) == 0;\n        if (ok) { memmove(s->g2b, ui_edit_str_g2b_ascii, (size_t)bytes); }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_str_move_to_heap(ui_edit_str_t* s, int32_t c) {\n    bool ok = true;\n    rt_assert(c >= s->b, \"can expand cannot shrink\");\n    if (s->c == 0) { // s->u points outside of the heap\n        const char* o = s->u;\n        ok = rt_heap.alloc((void**)&s->u, c) == 0;\n        if (ok) { memmove(s->u, o, (size_t)s->b); }\n    } else if (s->c < c) {\n        ok = rt_heap.realloc((void**)&s->u, c) == 0;\n    }\n    if (ok) { s->c = c; }\n    return ok;\n}\n\nstatic bool ui_edit_str_expand(ui_edit_str_t* s, int32_t c) {\n    rt_swear(c > 0);\n    bool ok = ui_edit_str_move_to_heap(s, c);\n    if (ok && c > s->c) {\n        if (rt_heap.realloc((void**)&s->u, c) == 0) {\n            s->c = c;\n        } else {\n            ok = false;\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_str_shrink(ui_edit_str_t* s) {\n    if (s->c > s->b) { // s->c == 0 for empty and single byte ASCII strings\n        rt_assert(s->u != ui_edit_str_empty_utf8);\n        if (s->b == 0) {\n            rt_heap.free(s->u);\n            s->u = ui_edit_str_empty_utf8;\n        } else {\n            bool ok = rt_heap.realloc((void**)&s->u, s->b) == 0;\n            rt_swear(ok, \"smaller size is always expected to be ok\");\n        }\n        s->c = s->b;\n    }\n    // Optimize memory for short ASCII only strings:\n    if (s->g2b != ui_edit_str_g2b_ascii) {\n        if (s->g == s->b && s->g < rt_countof(ui_edit_str_g2b_ascii) - 1) {\n            // If this is an ascii only utf8 string shorter than\n            // ui_edit_str_g2b_ascii it does not need .g2b[] allocated:\n            if (s->g2b != ui_edit_str_g2b_ascii) {\n                rt_heap.free(s->g2b);\n                s->g2b = ui_edit_str_g2b_ascii;\n            }\n        } else {\n//          const int32_t b64 = rt_min(s->b, 64);\n//          rt_println(\"none ASCII: .b:%d .g:%d %*.*s\", s->b, s->g, b64, b64, s->u);\n        }\n    }\n}\n\nstatic bool ui_edit_str_remove(ui_edit_str_t* s, int32_t f, int32_t t) {\n    bool ok = true; // optimistic approach\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    const int32_t bytes_to_remove = s->g2b[t] - s->g2b[f];\n    rt_assert(bytes_to_remove >= 0);\n    if (bytes_to_remove > 0) {\n        ok = ui_edit_str_move_to_heap(s, s->b);\n        if (ok) {\n            const int32_t bytes_to_shift = s->b - s->g2b[t];\n            rt_assert(0 <= bytes_to_shift && bytes_to_shift <= s->b);\n            memmove(s->u + s->g2b[f], s->u + s->g2b[t], (size_t)bytes_to_shift);\n            if (s->g2b != ui_edit_str_g2b_ascii) {\n                memmove(s->g2b + f, s->g2b + t,\n                        (size_t)(s->g - t + 1) * sizeof(int32_t));\n                for (int32_t i = f; i <= s->g; i++) {\n                    s->g2b[i] -= bytes_to_remove;\n                }\n            } else {\n                // no need to shrink g2b[] for ASCII only strings:\n                for (int32_t i = 0; i <= s->g; i++) { rt_assert(s->g2b[i] == i); }\n            }\n            s->b -= bytes_to_remove;\n            s->g -= t - f;\n        }\n    }\n    ui_edit_str_check(s);\n    return ok;\n}\n\nstatic bool ui_edit_str_replace(ui_edit_str_t* s,\n        int32_t f, int32_t t, const char* u, int32_t b) {\n    const int64_t _4_bytes = (int64_t)sizeof(int32_t);\n    bool ok = true; // optimistic approach\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    ui_edit_str_parameters(u, b);\n    // we are inserting \"b\" bytes and removing \"t - f\" glyphs\n    const int32_t bytes_to_remove = s->g2b[t] - s->g2b[f];\n    const int32_t bytes_to_insert = b; // only for readability\n    if (b == 0) { // just remove glyphs\n        ok = ui_edit_str_remove(s, f, t);\n    } else { // remove and insert\n        ui_edit_str_t ins = {0};\n        // ui_edit_str_init_ro() verifies utf-8 and calculates g2b[]:\n        ok = ui_edit_str_init(&ins, u, b, false);\n        const int32_t glyphs_to_insert = ins.g; // only for readability\n        const int32_t glyphs_to_remove = t - f; // only for readability\n        if (ok) {\n            const int32_t bytes = s->b + bytes_to_insert - bytes_to_remove;\n            rt_assert(ins.g2b != null); // pacify code analysis\n            rt_assert(bytes > 0);\n            const int32_t c = rt_max(s->b, bytes);\n            // keep g2b == ui_edit_str_g2b_ascii as much as possible\n            const bool all_ascii = s->g2b == ui_edit_str_g2b_ascii &&\n                                   ins.g2b == ui_edit_str_g2b_ascii &&\n                                   bytes < rt_countof(ui_edit_str_g2b_ascii) - 1;\n            ok = ui_edit_str_move_to_heap(s, c);\n            if (ok) {\n                if (!all_ascii) {\n                    ui_edit_str_move_g2b_to_heap(s);\n                }\n                // insert ui_edit_str_t \"ins\" at glyph position \"f\"\n                // reusing ins.u[0..ins.b-1] and ins.g2b[0..ins.g]\n                // moving memory using memmove() left to right:\n                if (bytes_to_insert <= bytes_to_remove) {\n                    memmove(s->u + s->g2b[f] + bytes_to_insert,\n                           s->u + s->g2b[f] + bytes_to_remove,\n                           (size_t)(s->b - s->g2b[f] - bytes_to_remove));\n                    if (all_ascii) {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                    } else {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        memmove(s->g2b + f + glyphs_to_insert,\n                               s->g2b + f + glyphs_to_remove,\n                               (size_t)(s->g - t + 1) * _4_bytes);\n                    }\n                    memmove(s->u + s->g2b[f], ins.u, (size_t)ins.b);\n                } else {\n                    if (all_ascii) {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                    } else {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        const int32_t g = s->g + glyphs_to_insert -\n                                                 glyphs_to_remove;\n                        rt_assert(g > s->g);\n                        ok = rt_heap.realloc(&s->g2b,\n                                             (size_t)(g + 1) * _4_bytes) == 0;\n                    }\n                    // need to shift bytes staring with s.g2b[t] toward the end\n                    if (ok) {\n                        memmove(s->u + s->g2b[f] + bytes_to_insert,\n                                s->u + s->g2b[f] + bytes_to_remove,\n                                (size_t)(s->b - s->g2b[f] - bytes_to_remove));\n                        if (all_ascii) {\n                            rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        } else {\n                            rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                            memmove(s->g2b + f + glyphs_to_insert,\n                                    s->g2b + f + glyphs_to_remove,\n                                    (size_t)(s->g - t + 1) * _4_bytes);\n                        }\n                        memmove(s->u + s->g2b[f], ins.u, (size_t)ins.b);\n                    }\n                }\n                if (ok) {\n                    if (!all_ascii) {\n                        rt_assert(s->g2b != null && s->g2b != ui_edit_str_g2b_ascii);\n                        for (int32_t i = f; i <= f + glyphs_to_insert; i++) {\n                            s->g2b[i] = ins.g2b[i - f] + s->g2b[f];\n                        }\n                    } else {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        for (int32_t i = f; i <= f + glyphs_to_insert; i++) {\n                            rt_assert(ui_edit_str_g2b_ascii[i] == i);\n                            rt_assert(ins.g2b[i - f] + s->g2b[f] == i);\n                        }\n                    }\n                    s->b += bytes_to_insert - bytes_to_remove;\n                    s->g += glyphs_to_insert - glyphs_to_remove;\n                    rt_assert(s->b == bytes);\n                    if (!all_ascii) {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        for (int32_t i = f + glyphs_to_insert + 1; i <= s->g; i++) {\n                            s->g2b[i] += bytes_to_insert - bytes_to_remove;\n                        }\n                        s->g2b[s->g] = s->b;\n                    } else {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        for (int32_t i = f + glyphs_to_insert + 1; i <= s->g; i++) {\n                            rt_assert(s->g2b[i] == i);\n                            rt_assert(ui_edit_str_g2b_ascii[i] == i);\n                        }\n                        rt_assert(s->g2b[s->g] == s->b);\n                    }\n                }\n            }\n            ui_edit_str_free(&ins);\n        }\n    }\n    ui_edit_str_shrink(s);\n    ui_edit_str_check(s);\n    return ok;\n}\n\nstatic bool ui_edit_str_is_zwj(uint32_t utf32) {\n    return utf32 == 0x200D;\n}\n\nstatic bool ui_edit_str_is_punctuation(uint32_t utf32) {\n    return\n        (utf32 >= 0x0021 && utf32 <= 0x0023) ||  // !\"#\n        (utf32 >= 0x0025 && utf32 <= 0x002A) ||  // %&'()*+\n        (utf32 >= 0x002C && utf32 <= 0x002F) ||  // ,-./\n        (utf32 >= 0x003A && utf32 <= 0x003B) ||  //:;\n        (utf32 >= 0x003F && utf32 <= 0x0040) ||  // ?@\n        (utf32 >= 0x005B && utf32 <= 0x005D) ||  // [\\]\n        (utf32 == 0x005F) ||                     // _\n        (utf32 == 0x007B) ||                     // {\n        (utf32 == 0x007D) ||                     // }\n        (utf32 == 0x007E) ||                     // ~\n        (utf32 >= 0x2000 && utf32 <= 0x206F) ||  // General Punctuation\n        (utf32 >= 0x3000 && utf32 <= 0x303F) ||  // CJK Symbols and Punctuation\n        (utf32 >= 0xFE30 && utf32 <= 0xFE4F) ||  // CJK Compatibility Forms\n        (utf32 >= 0xFE50 && utf32 <= 0xFE6F) ||  // Small Form Variants\n        (utf32 >= 0xFF01 && utf32 <= 0xFF0F) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF1A && utf32 <= 0xFF1F) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF3B && utf32 <= 0xFF3D) ||  // Fullwidth ASCII variants\n        (utf32 == 0xFF3F) ||                     // Fullwidth _\n        (utf32 >= 0xFF5B && utf32 <= 0xFF65);    // Fullwidth ASCII variants and halfwidth forms\n}\n\nstatic bool ui_edit_str_is_letter(uint32_t utf32) {\n    return\n        (utf32 >= 0x0041 && utf32 <= 0x005A) ||  // Latin uppercase\n        (utf32 >= 0x0061 && utf32 <= 0x007A) ||  // Latin lowercase\n        (utf32 >= 0x00C0 && utf32 <= 0x00D6) ||  // Latin-1 uppercase\n        (utf32 >= 0x00D8 && utf32 <= 0x00F6) ||  // Latin-1 lowercase\n        (utf32 >= 0x00F8 && utf32 <= 0x00FF) ||  // Latin-1 lowercase\n        (utf32 >= 0x0100 && utf32 <= 0x017F) ||  // Latin Extended-A\n        (utf32 >= 0x0180 && utf32 <= 0x024F) ||  // Latin Extended-B\n        (utf32 >= 0x0250 && utf32 <= 0x02AF) ||  // IPA Extensions\n        (utf32 >= 0x0370 && utf32 <= 0x03FF) ||  // Greek and Coptic\n        (utf32 >= 0x0400 && utf32 <= 0x04FF) ||  // Cyrillic\n        (utf32 >= 0x0500 && utf32 <= 0x052F) ||  // Cyrillic Supplement\n        (utf32 >= 0x0530 && utf32 <= 0x058F) ||  // Armenian\n        (utf32 >= 0x10A0 && utf32 <= 0x10FF) ||  // Georgian\n        (utf32 >= 0x0600 && utf32 <= 0x06FF) ||  // Arabic (covers Arabic, Kurdish, and Pashto)\n        (utf32 >= 0x0900 && utf32 <= 0x097F) ||  // Devanagari (covers Hindi)\n        (utf32 >= 0x0980 && utf32 <= 0x09FF) ||  // Bengali\n        (utf32 >= 0x0A00 && utf32 <= 0x0A7F) ||  // Gurmukhi (common in Northern India, related to Punjabi)\n        (utf32 >= 0x0B80 && utf32 <= 0x0BFF) ||  // Tamil\n        (utf32 >= 0x0C00 && utf32 <= 0x0C7F) ||  // Telugu\n        (utf32 >= 0x0C80 && utf32 <= 0x0CFF) ||  // Kannada\n        (utf32 >= 0x0D00 && utf32 <= 0x0D7F) ||  // Malayalam\n        (utf32 >= 0x0D80 && utf32 <= 0x0DFF) ||  // Sinhala\n        (utf32 >= 0x3040 && utf32 <= 0x309F) ||  // Hiragana (because it is syllabic)\n        (utf32 >= 0x30A0 && utf32 <= 0x30FF) ||  // Katakana\n        (utf32 >= 0x1E00 && utf32 <= 0x1EFF);    // Latin Extended Additional\n}\n\nstatic bool ui_edit_str_is_spacing(uint32_t utf32) {\n    return\n        (utf32 >= 0x02B0 && utf32 <= 0x02FF) ||  // Spacing Modifier Letters\n        (utf32 >= 0xA700 && utf32 <= 0xA71F);    // Modifier Tone Letters\n}\n\nstatic bool ui_edit_str_is_combining(uint32_t utf32) {\n    return\n        (utf32 >= 0x0300 && utf32 <= 0x036F) ||  // Combining Diacritical Marks\n        (utf32 >= 0x1AB0 && utf32 <= 0x1AFF) ||  // Combining Diacritical Marks Extended\n        (utf32 >= 0x1DC0 && utf32 <= 0x1DFF) ||  // Combining Diacritical Marks Supplement\n        (utf32 >= 0x20D0 && utf32 <= 0x20FF) ||  // Combining Diacritical Marks for Symbols\n        (utf32 >= 0xFE20 && utf32 <= 0xFE2F);    // Combining Half Marks\n}\n\nstatic bool ui_edit_str_is_blank(uint32_t utf32) {\n    return\n        (utf32 == 0x0009) ||  // Horizontal Tab\n        (utf32 == 0x000A) ||  // Line Feed\n        (utf32 == 0x000B) ||  // Vertical Tab\n        (utf32 == 0x000C) ||  // Form Feed\n        (utf32 == 0x000D) ||  // Carriage Return\n        (utf32 == 0x0020) ||  // Space\n        (utf32 == 0x0085) ||  // Next Line\n        (utf32 == 0x00A0) ||  // Non-breaking Space\n        (utf32 == 0x1680) ||  // Ogham Space Mark\n        (utf32 >= 0x2000 && utf32 <= 0x200A) ||  // En Quad to Hair Space\n        (utf32 == 0x2028) ||  // Line Separator\n        (utf32 == 0x2029) ||  // Paragraph Separator\n        (utf32 == 0x202F) ||  // Narrow No-Break Space\n        (utf32 == 0x205F) ||  // Medium Mathematical Space\n        (utf32 == 0x3000);    // Ideographic Space\n}\n\nstatic bool ui_edit_str_is_symbol(uint32_t utf32) {\n    return\n        (utf32 >= 0x0024 && utf32 <= 0x0024) ||  // Dollar sign\n        (utf32 >= 0x00A2 && utf32 <= 0x00A5) ||  // Cent sign to Yen sign\n        (utf32 >= 0x20A0 && utf32 <= 0x20CF) ||  // Currency Symbols\n        (utf32 >= 0x2100 && utf32 <= 0x214F) ||  // Letter like Symbols\n        (utf32 >= 0x2190 && utf32 <= 0x21FF) ||  // Arrows\n        (utf32 >= 0x2200 && utf32 <= 0x22FF) ||  // Mathematical Operators\n        (utf32 >= 0x2300 && utf32 <= 0x23FF) ||  // Miscellaneous Technical\n        (utf32 >= 0x2400 && utf32 <= 0x243F) ||  // Control Pictures\n        (utf32 >= 0x2440 && utf32 <= 0x245F) ||  // Optical Character Recognition\n        (utf32 >= 0x2460 && utf32 <= 0x24FF) ||  // Enclosed Alphanumeric\n        (utf32 >= 0x2500 && utf32 <= 0x257F) ||  // Box Drawing\n        (utf32 >= 0x2580 && utf32 <= 0x259F) ||  // Block Elements\n        (utf32 >= 0x25A0 && utf32 <= 0x25FF) ||  // Geometric Shapes\n        (utf32 >= 0x2600 && utf32 <= 0x26FF) ||  // Miscellaneous Symbols\n        (utf32 >= 0x2700 && utf32 <= 0x27BF) ||  // Dingbats\n        (utf32 >= 0x2900 && utf32 <= 0x297F) ||  // Supplemental Arrows-B\n        (utf32 >= 0x2B00 && utf32 <= 0x2BFF) ||  // Miscellaneous Symbols and Arrows\n        (utf32 >= 0xFB00 && utf32 <= 0xFB4F) ||  // Alphabetic Presentation Forms\n        (utf32 >= 0xFE50 && utf32 <= 0xFE6F) ||  // Small Form Variants\n        (utf32 >= 0xFF01 && utf32 <= 0xFF20) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF3B && utf32 <= 0xFF40) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF5B && utf32 <= 0xFF65);    // Fullwidth ASCII variants\n}\n\nstatic bool ui_edit_str_is_digit(uint32_t utf32) {\n    return\n        (utf32 >= 0x0030 && utf32 <= 0x0039) ||  // ASCII digits 0-9\n        (utf32 >= 0x0660 && utf32 <= 0x0669) ||  // Arabic-Indic digits\n        (utf32 >= 0x06F0 && utf32 <= 0x06F9) ||  // Extended Arabic-Indic digits\n        (utf32 >= 0x07C0 && utf32 <= 0x07C9) ||  // N'Ko digits\n        (utf32 >= 0x0966 && utf32 <= 0x096F) ||  // Devanagari digits\n        (utf32 >= 0x09E6 && utf32 <= 0x09EF) ||  // Bengali digits\n        (utf32 >= 0x0A66 && utf32 <= 0x0A6F) ||  // Gurmukhi digits\n        (utf32 >= 0x0AE6 && utf32 <= 0x0AEF) ||  // Gujarati digits\n        (utf32 >= 0x0B66 && utf32 <= 0x0B6F) ||  // Oriya digits\n        (utf32 >= 0x0BE6 && utf32 <= 0x0BEF) ||  // Tamil digits\n        (utf32 >= 0x0C66 && utf32 <= 0x0C6F) ||  // Telugu digits\n        (utf32 >= 0x0CE6 && utf32 <= 0x0CEF) ||  // Kannada digits\n        (utf32 >= 0x0D66 && utf32 <= 0x0D6F) ||  // Malayalam digits\n        (utf32 >= 0x0E50 && utf32 <= 0x0E59) ||  // Thai digits\n        (utf32 >= 0x0ED0 && utf32 <= 0x0ED9) ||  // Lao digits\n        (utf32 >= 0x0F20 && utf32 <= 0x0F29) ||  // Tibetan digits\n        (utf32 >= 0x1040 && utf32 <= 0x1049) ||  // Myanmar digits\n        (utf32 >= 0x17E0 && utf32 <= 0x17E9) ||  // Khmer digits\n        (utf32 >= 0x1810 && utf32 <= 0x1819) ||  // Mongolian digits\n        (utf32 >= 0xFF10 && utf32 <= 0xFF19);    // Fullwidth digits\n}\n\nstatic bool ui_edit_str_is_alphanumeric(uint32_t utf32) {\n    return ui_edit_str.is_letter(utf32) || ui_edit_str.is_digit(utf32);\n}\n\nstatic bool ui_edit_str_is_cjk_or_emoji(uint32_t utf32) {\n    return !ui_edit_str_is_letter(utf32) &&\n       ((utf32 >=  0x4E00 && utf32 <=  0x9FFF) || // CJK Unified Ideographs\n        (utf32 >=  0x3400 && utf32 <=  0x4DBF) || // CJK Unified Ideographs Extension A\n        (utf32 >= 0x20000 && utf32 <= 0x2A6DF) || // CJK Unified Ideographs Extension B\n        (utf32 >= 0x2A700 && utf32 <= 0x2B73F) || // CJK Unified Ideographs Extension C\n        (utf32 >= 0x2B740 && utf32 <= 0x2B81F) || // CJK Unified Ideographs Extension D\n        (utf32 >= 0x2B820 && utf32 <= 0x2CEAF) || // CJK Unified Ideographs Extension E\n        (utf32 >= 0x2CEB0 && utf32 <= 0x2EBEF) || // CJK Unified Ideographs Extension F\n        (utf32 >=  0xF900 && utf32 <=  0xFAFF) || // CJK Compatibility Ideographs\n        (utf32 >= 0x2F800 && utf32 <= 0x2FA1F) || // CJK Compatibility Ideographs Supplement\n        (utf32 >= 0x1F600 && utf32 <= 0x1F64F) || // Emoticons\n        (utf32 >= 0x1F300 && utf32 <= 0x1F5FF) || // Misc Symbols and Pictographs\n        (utf32 >= 0x1F680 && utf32 <= 0x1F6FF) || // Transport and Map\n        (utf32 >= 0x1F700 && utf32 <= 0x1F77F) || // Alchemical Symbols\n        (utf32 >= 0x1F780 && utf32 <= 0x1F7FF) || // Geometric Shapes Extended\n        (utf32 >= 0x1F800 && utf32 <= 0x1F8FF) || // Supplemental Arrows-C\n        (utf32 >= 0x1F900 && utf32 <= 0x1F9FF) || // Supplemental Symbols and Pictographs\n        (utf32 >= 0x1FA00 && utf32 <= 0x1FA6F) || // Chess Symbols\n        (utf32 >= 0x1FA70 && utf32 <= 0x1FAFF) || // Symbols and Pictographs Extended-A\n        (utf32 >= 0x1FB00 && utf32 <= 0x1FBFF));  // Symbols for Legacy Computing\n}\n\nstatic bool ui_edit_str_can_break(uint32_t cp1, uint32_t cp2) {\n    return !ui_edit_str.is_zwj(cp2) &&\n       (ui_edit_str.is_cjk_or_emoji(cp1) || ui_edit_str.is_cjk_or_emoji(cp2) ||\n        ui_edit_str.is_punctuation(cp1)  || ui_edit_str.is_punctuation(cp2)  ||\n        ui_edit_str.is_blank(cp1)        || ui_edit_str.is_blank(cp2)        ||\n        ui_edit_str.is_combining(cp1)    || ui_edit_str.is_combining(cp2)    ||\n        ui_edit_str.is_spacing(cp1)      || ui_edit_str.is_spacing(cp2));\n}\n\n#pragma push_macro(\"ui_edit_usd\")\n#pragma push_macro(\"ui_edit_gbp\")\n#pragma push_macro(\"ui_edit_euro\")\n#pragma push_macro(\"ui_edit_money_bag\")\n#pragma push_macro(\"ui_edit_pot_of_honey\")\n#pragma push_macro(\"ui_edit_gothic_hwair\")\n\n#define ui_edit_usd             \"\\x24\"\n#define ui_edit_gbp             \"\\xC2\\xA3\"\n#define ui_edit_euro            \"\\xE2\\x82\\xAC\"\n// https://www.compart.com/en/unicode/U+1F4B0\n#define ui_edit_money_bag       \"\\xF0\\x9F\\x92\\xB0\"\n// https://www.compart.com/en/unicode/U+1F36F\n#define ui_edit_pot_of_honey    \"\\xF0\\x9F\\x8D\\xAF\"\n// https://www.compart.com/en/unicode/U+10348\n#define ui_edit_gothic_hwair    \"\\xF0\\x90\\x8D\\x88\" // Gothic Letter Hwair\n\nstatic void ui_edit_str_test_replace(void) { // exhaustive permutations\n    // Exhaustive 9,765,625 replace permutations may take\n    // up to 5 minutes of CPU time in release.\n    // Recommended to be invoked at least once after making any\n    // changes to ui_edit_str.replace and around.\n    // Menu: Debug / Windows / Show Diagnostic Tools allows to watch\n    //       memory pressure for whole 3 minutes making sure code is\n    //       not leaking memory profusely.\n    const char* gs[] = { // glyphs\n        \"\", ui_edit_usd, ui_edit_gbp, ui_edit_euro, ui_edit_money_bag\n    };\n    const int32_t gb[] = {0, 1, 2, 3, 4}; // number of bytes per codepoint\n    enum { n = rt_countof(gs) };\n    int32_t npn = 1; // n to the power of n\n    for (int32_t i = 0; i < n; i++) { npn *= n; }\n    int32_t gix_src[n] = {0};\n    // 5^5 = 3,125   3,125 * 3,125 = 9,765,625\n    for (int32_t i = 0; i < npn; i++) {\n        int32_t vi = i;\n        for (int32_t j = 0; j < n; j++) {\n            gix_src[j] = vi % n;\n            vi /= n;\n        }\n        int32_t g2p[n + 1] = {0};\n        int32_t ngx = 1; // next glyph index\n        char src[128] = {0};\n        for (int32_t j = 0; j < n; j++) {\n            if (gix_src[j] > 0) {\n                strcat(src, gs[gix_src[j]]);\n                rt_assert(1 <= ngx && ngx <= n);\n                g2p[ngx] = g2p[ngx - 1] + gb[gix_src[j]];\n                ngx++;\n            }\n        }\n        if (i % 100 == 99) {\n            rt_println(\"%2d%% [%d][%d][%d][%d][%d] \"\n                    \"\\\"%s\\\",\\\"%s\\\",\\\"%s\\\",\\\"%s\\\",\\\"%s\\\": \\\"%s\\\"\",\n                (i * 100) / npn,\n                gix_src[0], gix_src[1], gix_src[2], gix_src[3], gix_src[4],\n                gs[gix_src[0]], gs[gix_src[1]], gs[gix_src[2]],\n                gs[gix_src[3]], gs[gix_src[4]], src);\n        }\n        ui_edit_str_t s = {0};\n        // reference constructor does not copy to heap:\n        bool ok = ui_edit_str_init(&s, src, -1, false);\n        rt_swear(ok);\n        for (int32_t f = 0; f <= s.g; f++) { // from\n            for (int32_t t = f; t <= s.g; t++) { // to\n                int32_t gix_rep[n] = {0};\n                // replace range [f, t] with all possible glyphs sequences:\n                for (int32_t k = 0; k < npn; k++) {\n                    int32_t vk = i;\n                    for (int32_t j = 0; j < n; j++) {\n                        gix_rep[j] = vk % n;\n                        vk /= n;\n                    }\n                    char rep[128] = {0};\n                    for (int32_t j = 0; j < n; j++) { strcat(rep, gs[gix_rep[j]]); }\n                    char e1[128] = {0}; // expected based on s.g2b[]\n                    snprintf(e1, rt_countof(e1), \"%.*s%s%.*s\",\n                        s.g2b[f], src,\n                        rep,\n                        s.b - s.g2b[t], src + s.g2b[t]\n                    );\n                    char e2[128] = {0}; // expected based on gs[]\n                    snprintf(e2, rt_countof(e1), \"%.*s%s%.*s\",\n                        g2p[f], src,\n                        rep,\n                        (int32_t)strlen(src) - g2p[t], src + g2p[t]\n                    );\n                    rt_swear(strcmp(e1, e2) == 0,\n                        \"s.u[%d:%d]: \\\"%.*s\\\" g:%d [%d:%d] rep=\\\"%s\\\" \"\n                        \"e1: \\\"%s\\\" e2: \\\"%s\\\"\",\n                        s.b, s.c, s.b, s.u, s.g, f, t, rep, e1, e2);\n                    ui_edit_str_t c = {0}; // copy\n                    ok = ui_edit_str_init(&c, src, -1, true);\n                    rt_swear(ok);\n                    ok = ui_edit_str_replace(&c, f, t, rep, -1);\n                    rt_swear(ok);\n                    rt_swear(memcmp(c.u, e1, c.b) == 0,\n                           \"s.u[%d:%d]: \\\"%.*s\\\" g:%d [%d:%d] rep=\\\"%s\\\" \"\n                           \"expected: \\\"%s\\\"\",\n                           s.b, s.c, s.b, s.u, s.g,\n                           f, t, rep, e1);\n                    ui_edit_str_free(&c);\n                }\n            }\n        }\n        ui_edit_str_free(&s);\n    }\n}\n\nstatic void ui_edit_str_test_glyph_bytes(void) {\n    #pragma push_macro(\"glyph_bytes_test\")\n    #define glyph_bytes_test(s, b, expectancy) \\\n        rt_swear(rt_str.utf8bytes(s, b) == expectancy)\n    // Valid Sequences\n    glyph_bytes_test(\"a\", 1, 1);\n    glyph_bytes_test(ui_edit_gbp, 2, 2);\n    glyph_bytes_test(ui_edit_euro, 3, 3);\n    glyph_bytes_test(ui_edit_gothic_hwair, 4, 4);\n    // Invalid Continuation Bytes\n    glyph_bytes_test(\"\\xC2\\x00\", 2, 0);\n    glyph_bytes_test(\"\\xE0\\x80\\x00\", 3, 0);\n    glyph_bytes_test(\"\\xF0\\x80\\x80\\x00\", 4, 0);\n    // Overlong Encodings\n    glyph_bytes_test(\"\\xC0\\xAF\", 2, 0); // '!'\n    glyph_bytes_test(\"\\xE0\\x9F\\xBF\", 3, 0); // upside down '?'\n    glyph_bytes_test(\"\\xF0\\x80\\x80\\xBF\", 4, 0); // '~'\n    // UTF-16 Surrogates\n    glyph_bytes_test(\"\\xED\\xA0\\x80\", 3, 0); // High surrogate\n    glyph_bytes_test(\"\\xED\\xBF\\xBF\", 3, 0); // Low surrogate\n    // Code Points Outside Valid Range\n    glyph_bytes_test(\"\\xF4\\x90\\x80\\x80\", 4, 0); // U+110000\n    // Invalid Initial Bytes\n    glyph_bytes_test(\"\\xC0\", 1, 0);\n    glyph_bytes_test(\"\\xC1\", 1, 0);\n    glyph_bytes_test(\"\\xF5\", 1, 0);\n    glyph_bytes_test(\"\\xFF\", 1, 0);\n    // 5-byte sequence (always invalid)\n    glyph_bytes_test(\"\\xF8\\x88\\x80\\x80\\x80\", 5, 0);\n    #pragma pop_macro(\"glyph_bytes_test\")\n}\n\nstatic void ui_edit_str_test(void) {\n    ui_edit_str_test_glyph_bytes();\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"hello\", -1, false);\n        rt_swear(ok);\n        rt_swear(s.b == 5 && s.c == 0 && memcmp(s.u, \"hello\", 5) == 0);\n        rt_swear(s.g == 5 && s.g2b != null);\n        for (int32_t i = 0; i <= s.g; i++) {\n            rt_swear(s.g2b[i] == i);\n        }\n        ui_edit_str_free(&s);\n    }\n    const char* currencies = ui_edit_usd  ui_edit_gbp\n                             ui_edit_euro ui_edit_money_bag;\n    const char* money = currencies;\n    {\n        ui_edit_str_t s = {0};\n        const int32_t n = (int32_t)strlen(currencies);\n        bool ok = ui_edit_str_init(&s, money, n, true);\n        rt_swear(ok);\n        rt_swear(s.b == n && s.c == s.b && memcmp(s.u, money, s.b) == 0);\n        rt_swear(s.g == 4 && s.g2b != null);\n        const int32_t g2b[] = {0, 1, 3, 6, 10};\n        for (int32_t i = 0; i <= s.g; i++) {\n            rt_swear(s.g2b[i] == g2b[i]);\n        }\n        ui_edit_str_free(&s);\n    }\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"hello\", -1, false);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 1, 4, null, 0);\n        rt_swear(ok);\n        rt_swear(s.b == 2 && memcmp(s.u, \"ho\", 2) == 0);\n        rt_swear(s.g == 2 && s.g2b[0] == 0 && s.g2b[1] == 1 && s.g2b[2] == 2);\n        ui_edit_str_free(&s);\n    }\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"Hello world\", -1, false);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 5, 6, \" cruel \", -1);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 0, 5, \"Goodbye\", -1);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, s.g - 5, s.g, \"Universe\", -1);\n        rt_swear(ok);\n        rt_swear(s.g == 22 && s.g2b[0] == 0 && s.g2b[s.g] == s.b);\n        for (int32_t i = 1; i < s.g; i++) {\n            rt_swear(s.g2b[i] == i); // because every glyph is ASCII\n        }\n        rt_swear(memcmp(s.u, \"Goodbye cruel Universe\", 22) == 0);\n        ui_edit_str_free(&s);\n    }\n    #ifdef UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n        ui_edit_str_test_replace();\n    #else\n        (void)(void*)ui_edit_str_test_replace; // mitigate unused warning\n    #endif\n}\n\n#pragma push_macro(\"ui_edit_gothic_hwair\")\n#pragma push_macro(\"ui_edit_pot_of_honey\")\n#pragma push_macro(\"ui_edit_money_bag\")\n#pragma push_macro(\"ui_edit_euro\")\n#pragma push_macro(\"ui_edit_gbp\")\n#pragma push_macro(\"ui_edit_usd\")\n\n#pragma pop_macro(\"ui_edit_str_parameters\")\n#pragma pop_macro(\"ui_edit_str_check_empty\")\n#pragma pop_macro(\"ui_edit_check_zeros\")\n#pragma pop_macro(\"ui_edit_str_check_from_to\")\n#pragma pop_macro(\"ui_edit_str_check\")\n\n#ifdef UI_EDIT_STR_TEST\n    rt_static_init(ui_edit_str) { ui_edit_str.test(); }\n#endif\n\n// tests:\n\nstatic void ui_edit_doc_test_big_text(void) {\n    enum { MB10 = 10 * 1000 * 1000 };\n    char* text = null;\n    rt_heap.alloc(&text, MB10);\n    memset(text, 'a', (size_t)MB10 - 1);\n    char* p = text;\n    uint32_t seed = 0x1;\n    for (;;) {\n        int32_t n = rt_num.random32(&seed) % 40 + 40;\n        if (p + n >= text + MB10) { break; }\n        p += n;\n        *p = '\\n';\n    }\n    text[MB10 - 1] = 0x00;\n    ui_edit_text_t t = {0};\n    bool ok = ui_edit_text.init(&t, text, MB10, false);\n    rt_swear(ok);\n    ui_edit_text.dispose(&t);\n    rt_heap.free(text);\n}\n\nstatic void ui_edit_doc_test_paragraphs(void) {\n    // ui_edit_doc_to_paragraphs() is about 1 microsecond\n    for (int i = 0; i < 100; i++)\n    {\n        {   // empty string to paragraphs:\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, null, 0, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 1);\n            rt_swear(t.ps[0].u[0] == 0 &&\n                  t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 0 &&\n                  t.ps[0].g == 0);\n            ui_edit_text.dispose(&t);\n        }\n        {   // string without \"\\n\"\n            const char* hello = \"hello\";\n            const int32_t n = (int32_t)strlen(hello);\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, n, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 1);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == n);\n            rt_swear(t.ps[0].g == n);\n            ui_edit_text.dispose(&t);\n        }\n        {   // string with \"\\n\" at the end\n            const char* hello = \"hello\\n\";\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, -1, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 2);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 5);\n            rt_swear(t.ps[0].g == 5);\n            rt_swear(t.ps[1].u[0] == 0x00);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[1].b == 0);\n            rt_swear(t.ps[1].g == 0);\n            ui_edit_text.dispose(&t);\n        }\n        {   // two string separated by \"\\n\"\n            const char* hello = \"hello\\nworld\";\n            const char* world = hello + 6;\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, -1, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 2);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 5);\n            rt_swear(t.ps[0].g == 5);\n            rt_swear(t.ps[1].u == world);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[1].b == 5);\n            rt_swear(t.ps[1].g == 5);\n            ui_edit_text.dispose(&t);\n        }\n    }\n    for (int i = 0; i < 10; i++) {\n        ui_edit_doc_test_big_text();\n    }\n}\n\ntypedef struct ui_edit_doc_test_notify_s {\n    ui_edit_notify_t notify;\n    int32_t count_before;\n    int32_t count_after;\n} ui_edit_doc_test_notify_t;\n\nstatic void ui_edit_doc_test_before(ui_edit_notify_t* n,\n        const ui_edit_notify_info_t* rt_unused(ni)) {\n    ui_edit_doc_test_notify_t* notify = (ui_edit_doc_test_notify_t*)n;\n    notify->count_before++;\n}\n\nstatic void ui_edit_doc_test_after(ui_edit_notify_t* n,\n        const ui_edit_notify_info_t* rt_unused(ni)) {\n    ui_edit_doc_test_notify_t* notify = (ui_edit_doc_test_notify_t*)n;\n    notify->count_after++;\n}\n\nstatic struct {\n    ui_edit_notify_t notify;\n} ui_edit_doc_test_notify;\n\n\nstatic void ui_edit_doc_test_0(void) {\n    ui_edit_doc_t edit_doc = {0};\n    ui_edit_doc_t* d = &edit_doc;\n    rt_swear(ui_edit_doc.init(d, null, 0, false));\n    ui_edit_text_t ins_text = {0};\n    rt_swear(ui_edit_text.init(&ins_text, \"a\", 1, false));\n    ui_edit_to_do_t undo = {0};\n    rt_swear(ui_edit_text.replace(&d->text, null, &ins_text, &undo));\n    ui_edit_doc.dispose_to_do(&undo);\n    ui_edit_text.dispose(&ins_text);\n    ui_edit_doc.dispose(d);\n}\n\nstatic void ui_edit_doc_test_1(void) {\n    ui_edit_doc_t edit_doc = {0};\n    ui_edit_doc_t* d = &edit_doc;\n    rt_swear(ui_edit_doc.init(d, null, 0, false));\n    ui_edit_text_t ins_text = {0};\n    rt_swear(ui_edit_text.init(&ins_text, \"a\", 1, false));\n    ui_edit_to_do_t undo = {0};\n    rt_swear(ui_edit_text.replace(&d->text, null, &ins_text, &undo));\n    ui_edit_doc.dispose_to_do(&undo);\n    ui_edit_text.dispose(&ins_text);\n    ui_edit_doc.dispose(d);\n}\n\nstatic void ui_edit_doc_test_2(void) {\n    {   // two string separated by \"\\n\"\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        ui_edit_notify_t notify1 = {0};\n        ui_edit_notify_t notify2 = {0};\n        ui_edit_doc_test_notify_t before_and_after = {0};\n        before_and_after.notify.before = ui_edit_doc_test_before;\n        before_and_after.notify.after  = ui_edit_doc_test_after;\n        ui_edit_doc.subscribe(d, &notify1);\n        ui_edit_doc.subscribe(d, &before_and_after.notify);\n        ui_edit_doc.subscribe(d, &notify2);\n        rt_swear(ui_edit_doc.bytes(d, null) == 0, \"expected empty\");\n        const char* hello = \"hello\\nworld\";\n        rt_swear(ui_edit_doc.replace(d, null, hello, -1));\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        rt_swear(t.np == 2);\n        rt_swear(t.ps[0].b == 5);\n        rt_swear(t.ps[0].g == 5);\n        rt_swear(memcmp(t.ps[0].u, \"hello\", 5) == 0);\n        rt_swear(t.ps[1].b == 5);\n        rt_swear(t.ps[1].g == 5);\n        rt_swear(memcmp(t.ps[1].u, \"world\", 5) == 0);\n        ui_edit_text.dispose(&t);\n        ui_edit_doc.unsubscribe(d, &notify1);\n        ui_edit_doc.unsubscribe(d, &before_and_after.notify);\n        ui_edit_doc.unsubscribe(d, &notify2);\n        ui_edit_doc.dispose(d);\n    }\n    // TODO: \"GoodbyeCruelUniverse\" insert 2x\"\\n\" splitting in 3 paragraphs\n    {   // three string separated by \"\\n\"\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        const char* s = \"Goodbye\" \"\\n\" \"Cruel\" \"\\n\" \"Universe\";\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        ui_edit_text.dispose(&t);\n        ui_edit_range_t r = { .from = {.pn = 0, .gp = 4},\n                              .to   = {.pn = 2, .gp = 3} };\n        rt_swear(ui_edit_doc.replace(d, &r, null, 0));\n        rt_swear(d->text.np == 1);\n        rt_swear(d->text.ps[0].b == 9);\n        rt_swear(d->text.ps[0].g == 9);\n        rt_swear(memcmp(d->text.ps[0].u, \"Goodverse\", 9) == 0);\n        rt_swear(ui_edit_doc.replace(d, null, null, 0)); // remove all\n        rt_swear(d->text.np == 1);\n        rt_swear(d->text.ps[0].b == 0);\n        rt_swear(d->text.ps[0].g == 0);\n        ui_edit_doc.dispose(d);\n    }\n    // TODO: \"GoodbyeCruelUniverse\" insert 2x\"\\n\" splitting in 3 paragraphs\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        const char* ins[] = { \"X\\nY\", \"X\\n\", \"\\nY\", \"\\n\", \"X\\nY\\nZ\" };\n        for (int32_t i = 0; i < rt_countof(ins); i++) {\n            rt_swear(ui_edit_doc.init(d, null, 0, false));\n            const char* s = \"GoodbyeCruelUniverse\";\n            rt_swear(ui_edit_doc.replace(d, null, s, -1));\n            ui_edit_range_t r = { .from = {.pn = 0, .gp =  7},\n                                  .to   = {.pn = 0, .gp = 12} };\n            ui_edit_text_t ins_text = {0};\n            ui_edit_text.init(&ins_text, ins[i], -1, false);\n            ui_edit_to_do_t undo = {0};\n            rt_swear(ui_edit_text.replace(&d->text, &r, &ins_text, &undo));\n            ui_edit_to_do_t redo = {0};\n            rt_swear(ui_edit_text.replace(&d->text, &undo.range, &undo.text, &redo));\n            ui_edit_doc.dispose_to_do(&undo);\n            undo.range = (ui_edit_range_t){0};\n            rt_swear(ui_edit_text.replace(&d->text, &redo.range, &redo.text, &undo));\n            ui_edit_doc.dispose_to_do(&redo);\n            ui_edit_doc.dispose_to_do(&undo);\n            ui_edit_text.dispose(&ins_text);\n            ui_edit_doc.dispose(d);\n        }\n    }\n}\n\nstatic void ui_edit_doc_test_3(void) {\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        ui_edit_doc_test_notify_t before_and_after = {0};\n        before_and_after.notify.before = ui_edit_doc_test_before;\n        before_and_after.notify.after  = ui_edit_doc_test_after;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        rt_swear(ui_edit_doc.subscribe(d, &before_and_after.notify));\n        const char* s = \"Goodbye Cruel Universe\";\n        const int32_t before = before_and_after.count_before;\n        const int32_t after  = before_and_after.count_after;\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        const int32_t bytes = (int32_t)strlen(s);\n        rt_swear(before + 1 == before_and_after.count_before);\n        rt_swear(after  + 1 == before_and_after.count_after);\n        rt_swear(d->text.np == 1);\n        rt_swear(ui_edit_doc.bytes(d, null) == bytes);\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        rt_swear(t.np == 1);\n        rt_swear(t.ps[0].b == bytes);\n        rt_swear(t.ps[0].g == bytes);\n        rt_swear(memcmp(t.ps[0].u, s, t.ps[0].b) == 0);\n        // with \"\\n\" and 0x00 at the end:\n        int32_t utf8bytes = ui_edit_doc.utf8bytes(d, null);\n        char* p = null;\n        rt_swear(rt_heap.alloc((void**)&p, utf8bytes) == 0);\n        p[utf8bytes - 1] = 0xFF;\n        ui_edit_doc.copy(d, null, p, utf8bytes);\n        rt_swear(p[utf8bytes - 1] == 0x00);\n        rt_swear(memcmp(p, s, bytes) == 0);\n        rt_heap.free(p);\n        ui_edit_text.dispose(&t);\n        ui_edit_doc.unsubscribe(d, &before_and_after.notify);\n        ui_edit_doc.dispose(d);\n    }\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        const char* s =\n            \"Hello World\"\n            \"\\n\"\n            \"Goodbye Cruel Universe\";\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        rt_swear(ui_edit_doc.undo(d));\n        rt_swear(ui_edit_doc.bytes(d, null) == 0);\n        rt_swear(ui_edit_doc.utf8bytes(d, null) == 1);\n        rt_swear(ui_edit_doc.redo(d));\n        {\n            int32_t utf8bytes = ui_edit_doc.utf8bytes(d, null);\n            char* p = null;\n            rt_swear(rt_heap.alloc((void**)&p, utf8bytes) == 0);\n            p[utf8bytes - 1] = 0xFF;\n            ui_edit_doc.copy(d, null, p, utf8bytes);\n            rt_swear(p[utf8bytes - 1] == 0x00);\n            rt_swear(memcmp(p, s, utf8bytes) == 0);\n            rt_heap.free(p);\n        }\n        ui_edit_doc.dispose(d);\n    }\n}\n\nstatic void ui_edit_doc_test_4(void) {\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        ui_edit_range_t r = {0};\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"a\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"b\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"c\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        ui_edit_doc.dispose(d);\n    }\n}\n\nstatic void ui_edit_doc_test(void) {\n    {\n        ui_edit_range_t r = { .from = {0,0}, .to = {0,0} };\n        rt_static_assertion(sizeof(r.from) + sizeof(r.from) == sizeof(r.a));\n        rt_swear(&r.from == &r.a[0] && &r.to == &r.a[1]);\n    }\n    #ifdef UI_EDIT_DOC_TEST_PARAGRAPHS\n        ui_edit_doc_test_paragraphs();\n    #else\n        (void)(void*)ui_edit_doc_test_paragraphs; // unused\n    #endif\n    // use n = 10,000,000 and Diagnostic Tools to watch for memory leaks\n    enum { n = 1000 };\n//  enum { n = 10 * 1000 * 1000 };\n    for (int32_t i = 0; i < n; i++) {\n        ui_edit_doc_test_0();\n        ui_edit_doc_test_1();\n        ui_edit_doc_test_2();\n        ui_edit_doc_test_3();\n        ui_edit_doc_test_4();\n    }\n}\n\nstatic const ui_edit_range_t ui_edit_invalid_range = {\n    .from = { .pn = -1, .gp = -1},\n    .to   = { .pn = -1, .gp = -1}\n};\n\nui_edit_range_if ui_edit_range = {\n    .compare       = ui_edit_range_compare,\n    .order         = ui_edit_range_order,\n    .is_valid      = ui_edit_range_is_valid,\n    .is_empty      = ui_edit_range_is_empty,\n    .uint64        = ui_edit_range_uint64,\n    .pg            = ui_edit_range_pg,\n    .inside        = ui_edit_range_inside_text,\n    .intersect     = ui_edit_range_intersect,\n    .invalid_range = &ui_edit_invalid_range\n};\n\nui_edit_text_if ui_edit_text = {\n    .init          = ui_edit_text_init,\n    .bytes         = ui_edit_text_bytes,\n    .all_on_null   = ui_edit_text_all_on_null,\n    .ordered       = ui_edit_text_ordered,\n    .end           = ui_edit_text_end,\n    .end_range     = ui_edit_text_end_range,\n    .dup           = ui_edit_text_dup,\n    .equal         = ui_edit_text_equal,\n    .copy_text     = ui_edit_text_copy_text,\n    .copy          = ui_edit_text_copy,\n    .replace       = ui_edit_text_replace,\n    .replace_utf8  = ui_edit_text_replace_utf8,\n    .dispose       = ui_edit_text_dispose\n};\n\nui_edit_doc_if ui_edit_doc = {\n    .init               = ui_edit_doc_init,\n    .replace            = ui_edit_doc_replace,\n    .bytes              = ui_edit_doc_bytes,\n    .copy_text          = ui_edit_doc_copy_text,\n    .utf8bytes          = ui_edit_doc_utf8bytes,\n    .copy               = ui_edit_doc_copy,\n    .redo               = ui_edit_doc_redo,\n    .undo               = ui_edit_doc_undo,\n    .subscribe          = ui_edit_doc_subscribe,\n    .unsubscribe        = ui_edit_doc_unsubscribe,\n    .dispose_to_do      = ui_edit_doc_dispose_to_do,\n    .dispose            = ui_edit_doc_dispose,\n    .test               = ui_edit_doc_test\n};\n\n#pragma push_macro(\"ui_edit_doc_dump\")\n#pragma push_macro(\"ui_edit_text_dump\")\n#pragma push_macro(\"ui_edit_range_dump\")\n#pragma push_macro(\"ui_edit_pg_dump\")\n#pragma push_macro(\"ui_edit_check_range_inside_text\")\n#pragma push_macro(\"ui_edit_check_pg_inside_text\")\n#pragma push_macro(\"ui_edit_check_zeros\")\n\n#ifdef UI_EDIT_DOC_TEST\n    rt_static_init(ui_edit_doc) { ui_edit_doc.test(); }\n#endif\n\n// ______________________________ ui_edit_view.c ______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n\n// TODO: find all \"== dt->np\" it is wrong pn < dt->np fix them all\n// TODO: undo/redo coalescing\n// TODO: back/forward navigation\n// TODO: exit (Ctrl+W?)/save(Ctrl+S, Ctrl+Shift+S) keyboard shortcuts?\n// TODO: ctrl left, ctrl right jump word ctrl+shift left/right select word?\n// TODO: iBeam cursor (definitely yes - see how MSVC does it)\n// TODO: vertical scrollbar ui\n// TODO: horizontal scroll: trivial to implement:\n//       add horizontal_scroll to e->w and paint\n//       paragraphs in a horizontally shifted clip\n\n// http://worrydream.com/refs/Tesler%20-%20A%20Personal%20History%20of%20Modeless%20Text%20Editing%20and%20Cut-Copy-Paste.pdf\n// https://web.archive.org/web/20221216044359/http://worrydream.com/refs/Tesler%20-%20A%20Personal%20History%20of%20Modeless%20Text%20Editing%20and%20Cut-Copy-Paste.pdf\n\n// Rich text options that are not addressed yet:\n// * Color of ranges (useful for code editing)\n// * Soft line breaks inside the paragraph (useful for e.g. bullet lists of options)\n// * Bold/Italic/Underline (along with color ranges)\n// * Multiple fonts (as long as run vertical size is the maximum of font)\n// * Kerning (?! like in overhung \"Fl\")\n\n// When implementation and header are amalgamated\n// into a single file header library name_space is\n// used to separate different modules namespaces.\n\ntypedef  struct ui_edit_glyph_s {\n    const char* s;\n    int32_t bytes;\n} ui_edit_glyph_t;\n\nstatic void ui_edit_layout(ui_view_t* v);\nstatic ui_point_t ui_edit_pg_to_xy(ui_edit_view_t* e, const ui_edit_pg_t pg);\n\n// Glyphs in monospaced Windows fonts may have different width for non-ASCII\n// characters. Thus even if edit is monospaced glyph measurements are used\n// in text layout.\n\nstatic void ui_edit_invalidate_parent(const ui_edit_view_t* e, const ui_rect_t* rc) {\n    // For transparent background of edit_view parent must draw background.\n    // In the current implementation invalidate() causes whole stack redraw\n    // in rectangle thus it does not matter much. But if it is ever optimized\n    // it will matter.\n    ui_color_t b = e->background;\n    if (ui_color_is_undefined(b) || ui_color_is_transparent(b)) {\n        ui_view.invalidate(e->parent, rc);\n    }\n}\n\nstatic void ui_edit_invalidate_rect(const ui_edit_view_t* e, const ui_rect_t rc) {\n    rt_assert(rc.w >= 0 && rc.h > 0); // w may be zero for empty selection\n    if (rc.w > 0 && rc.h > 0) {\n        ui_view.invalidate(&e->view, &rc);\n        ui_edit_invalidate_parent(e, &rc);\n    }\n}\n\nstatic void ui_edit_invalidate_view(const ui_edit_view_t* e) {\n    ui_view.invalidate(&e->view, null);\n    ui_edit_invalidate_parent(e, null);\n}\n\nstatic int32_t ui_edit_line_height(ui_edit_view_t* e) {\n    // at 96dpi:\n    // \"Segoe UI\" height + line_gap: 16\n    // ui_app.fm.prop h: 15 pt: 11.250 a:  3 c:  9 d: 3 bl: 12 il: 3 lg: 2\n    // \"Cascadia Mono\" height + line_gap: 17\n    // ui_app.fm.mono h: 16 pt: 12.000 a:  2 c: 11 d: 3 bl: 13 il: 4 lg: 0\n    return e->fm->height + e->fm->line_gap;\n}\n\nstatic ui_rect_t ui_edit_selection_rect(ui_edit_view_t* e) {\n    const ui_edit_range_t r = ui_edit_range.order(e->selection);\n    const ui_ltrb_t i = ui_view.margins(&e->view, &e->insets);\n    const ui_point_t p0 = ui_edit_pg_to_xy(e, r.from);\n    const ui_point_t p1 = ui_edit_pg_to_xy(e, r.to);\n    if (p0.x < 0 || p1.x < 0) { // selection outside of visible area\n        return (ui_rect_t) { .x = 0, .y = 0, .w = e->w, .h = e->h };\n    } else if (p0.y == p1.y) {\n        const int32_t max_w = rt_max(e->fm->max_char_width, e->fm->em.w);\n        int32_t w = p1.x - p0.x != 0 ?\n                p1.x - p0.x + max_w : e->caret_width;\n        return (ui_rect_t) { .x = p0.x, .y = i.top + p0.y,\n                             .w = w, .h = ui_edit_line_height(e) };\n    } else {\n        const int32_t h = p1.y - p0.y + ui_edit_line_height(e);\n        return (ui_rect_t) { .x = 0, .y = i.top + p0.y,\n                             .w = e->w, .h = h };\n    }\n}\n\n#if 0\nstatic void ui_edit_text_width_gp(ui_edit_view_t* e, const char* utf8, int32_t bytes) {\n    const int32_t glyphs = rt_str.glyphs(utf8, bytes);\n    rt_println(\"\\\"%.*s\\\" bytes:%d glyphs:%d\", bytes, utf8, bytes, glyphs);\n    int32_t* x = (int32_t*)rt_stackalloc((glyphs + 1) * sizeof(int32_t));\n    const ui_gdi_ta_t ta = { .fm = e->fm };\n    ui_wh_t wh = ui_gdi.glyphs_placement(&ta, utf8,  bytes, x, glyphs);\n//  rt_println(\"wh: %dx%d\", wh.w, wh.h);\n}\n#endif\n\nstatic int32_t ui_edit_text_width(ui_edit_view_t* e, const char* s, int32_t n) {\n//  fp64_t time = rt_clock.seconds();\n    // average GDI measure_text() performance per character:\n    // \"ui_app.fm.mono\"    ~500us (microseconds)\n    // \"ui_app.fm.prop.normal\" ~250us (microseconds) DirectWrite ~100us\n    const ui_gdi_ta_t ta = { .fm = e->fm, .color = e->color,\n                             .measure = true };\n    int32_t x = n == 0 ? 0 : ui_gdi.text(&ta, 0, 0, \"%.*s\", n, s).w;\n//  time = (rt_clock.seconds() - time) * 1000.0;\n//  static fp64_t time_sum;\n//  static fp64_t length_sum;\n//  time_sum += time;\n//  length_sum += n;\n//  rt_println(\"avg=%.6fms per char total %.3fms\", time_sum / length_sum, time_sum);\n    return x;\n}\n\nstatic int32_t ui_edit_word_break_at(ui_edit_view_t* e, int32_t pn, int32_t rn,\n        const int32_t width, bool allow_zero) {\n    // TODO: in sqlite.c 257,674 lines it takes 11 seconds to get all runs()\n    //       on average ui_edit_word_break_at() takes 4 x ui_edit_text_width()\n    //       measurements and they are slow. If we can reduce this amount\n    //       (not clear how) at least 2 times it will be a win.\n    //       Another way is background thread runs() processing but this is\n    //       involving a lot of complexity.\n    //       MSVC devenv.exe edits sqlite3.c w/o any visible delays\n    int32_t count = 0; // stats logging\n    int32_t chars = 0;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    ui_edit_paragraph_t* p = &e->para[pn];\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t k = 1; // at least 1 glyph\n    // offsets inside a run in glyphs and bytes from start of the paragraph:\n    int32_t gp = p->run[rn].gp;\n    int32_t bp = p->run[rn].bp;\n    if (gp < str->g - 1) {\n        const char* text = str->u + bp;\n        const int32_t glyphs_in_this_run = str->g - gp;\n        int32_t* g2b = &str->g2b[gp];\n        // 4 is maximum number of bytes in a UTF-8 sequence\n        int32_t gc = rt_min(4, glyphs_in_this_run);\n        int32_t w = ui_edit_text_width(e, text, g2b[gc] - bp);\n        count++;\n        chars += g2b[gc] - bp;\n        while (gc < glyphs_in_this_run && w < width) {\n            gc = rt_min(gc * 4, glyphs_in_this_run);\n            w = ui_edit_text_width(e, text, g2b[gc] - bp);\n            count++;\n            chars += g2b[gc] - bp;\n        }\n        if (w < width) {\n            k = gc;\n            rt_assert(1 <= k && k <= str->g - gp);\n        } else {\n            int32_t i = 0;\n            int32_t j = gc;\n            k = (i + j) / 2;\n            while (i < j) {\n                rt_assert(allow_zero || 1 <= k && k < gc + 1);\n                const int32_t n = g2b[k + 1] - bp;\n                int32_t px = ui_edit_text_width(e, text, n);\n                count++;\n                chars += n;\n                if (px == width) { break; }\n                if (px < width) { i = k + 1; } else { j = k; }\n                if (!allow_zero && (i + j) / 2 == 0) { break; }\n                k = (i + j) / 2;\n                rt_assert(allow_zero || 1 <= k && k <= str->g - gp);\n            }\n        }\n    }\n    rt_assert(allow_zero || 1 <= k && k <= str->g - gp);\n    return k;\n}\n\nstatic int32_t ui_edit_word_break(ui_edit_view_t* e, int32_t pn, int32_t rn) {\n    return ui_edit_word_break_at(e, pn, rn, e->edit.w, false);\n}\n\nstatic int32_t ui_edit_glyph_at_x(ui_edit_view_t* e, int32_t pn, int32_t rn,\n        int32_t x) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    if (x == 0 || dt->ps[pn].b == 0) {\n        return 0;\n    } else {\n        return ui_edit_word_break_at(e, pn, rn, x + 1, true);\n    }\n}\n\nstatic ui_edit_glyph_t ui_edit_glyph_at(ui_edit_view_t* e, ui_edit_pg_t p) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_glyph_t g = { .s = \"\", .bytes = 0 };\n    rt_assert(0 <= p.pn && p.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[p.pn];\n    const int32_t bytes = str->b;\n    const char* s = str->u;\n    const int32_t bp = str->g2b[p.gp];\n    if (bp < bytes) {\n        g.s = s + bp;\n        g.bytes = rt_str.utf8bytes(g.s, bytes - bp);\n        rt_swear(g.bytes > 0);\n    }\n    return g;\n}\n\n// paragraph_runs() breaks paragraph into `runs` according to `width`\n\nstatic const ui_edit_run_t* ui_edit_paragraph_runs(ui_edit_view_t* e, int32_t pn,\n        int32_t* runs) {\n//  fp64_t time = rt_clock.seconds();\n    rt_assert(e->w > 0);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_run_t* r = null;\n    if (e->para[pn].run != null) {\n        *runs = e->para[pn].runs;\n        r = e->para[pn].run;\n    } else {\n        rt_assert(0 <= pn && pn < dt->np);\n        ui_edit_paragraph_t* p = &e->para[pn];\n        const ui_edit_str_t* str = &dt->ps[pn];\n        if (p->run == null) {\n            rt_assert(p->runs == 0 && p->run == null);\n            const int32_t max_runs = str->b + 1;\n            bool ok = rt_heap.alloc((void**)&p->run, max_runs *\n                                    sizeof(ui_edit_run_t)) == 0;\n            rt_swear(ok);\n            ui_edit_run_t* run = p->run;\n            run[0].bp = 0;\n            run[0].gp = 0;\n            int32_t gc = str->b == 0 ? 0 : ui_edit_word_break(e, pn, 0);\n            if (gc == str->g) { // whole paragraph fits into width\n                p->runs = 1;\n                run[0].bytes  = str->b;\n                run[0].glyphs = str->g;\n                int32_t pixels = ui_edit_text_width(e, str->u, str->g2b[gc]);\n                run[0].pixels = pixels;\n            } else {\n                rt_assert(gc < str->g);\n                int32_t rc = 0; // runs count\n                int32_t ix = 0; // glyph index from to start of paragraph\n                const char* text = str->u;\n                int32_t bytes = str->b;\n                while (bytes > 0) {\n                    rt_assert(rc < max_runs);\n                    run[rc].bp = (int32_t)(text - str->u);\n                    run[rc].gp = ix;\n                    int32_t glyphs = ui_edit_word_break(e, pn, rc);\n                    int32_t utf8bytes = str->g2b[ix + glyphs] - run[rc].bp;\n                    int32_t pixels = ui_edit_text_width(e, text, utf8bytes);\n                    if (glyphs > 1 && utf8bytes < bytes && text[utf8bytes - 1] != 0x20) {\n                        // try to find word break SPACE character. utf8 space is 0x20\n                        int32_t i = utf8bytes;\n                        while (i > 0 && text[i - 1] != 0x20) { i--; }\n                        if (i > 0 && i != utf8bytes) {\n                            utf8bytes = i;\n                            glyphs = rt_str.glyphs(text, utf8bytes);\n                            rt_assert(glyphs >= 0);\n                            pixels = ui_edit_text_width(e, text, utf8bytes);\n                        }\n                    }\n                    run[rc].bytes  = utf8bytes;\n                    run[rc].glyphs = glyphs;\n                    run[rc].pixels = pixels;\n                    rc++;\n                    text += utf8bytes;\n                    rt_assert(0 <= utf8bytes && utf8bytes <= bytes);\n                    bytes -= utf8bytes;\n                    ix += glyphs;\n                }\n                rt_assert(rc > 0);\n                p->runs = rc; // truncate heap capacity array:\n                ok = rt_heap.realloc((void**)&p->run, rc * sizeof(ui_edit_run_t)) == 0;\n                rt_swear(ok);\n            }\n        }\n        *runs = p->runs;\n        r = p->run;\n    }\n    rt_assert(r != null && *runs >= 1);\n    return r;\n}\n\nstatic int32_t ui_edit_paragraph_run_count(ui_edit_view_t* e, int32_t pn) {\n    rt_swear(e->w > 0);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    int32_t runs = 0;\n    if (e->w > 0 && 0 <= pn && pn < dt->np) {\n        (void)ui_edit_paragraph_runs(e, pn, &runs);\n    }\n    return runs;\n}\n\nstatic int32_t ui_edit_glyphs_in_paragraph(ui_edit_view_t* e, int32_t pn) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    (void)ui_edit_paragraph_run_count(e, pn); // word break into runs\n    return dt->ps[pn].g;\n}\n\nstatic void ui_edit_create_caret(ui_edit_view_t* e) {\n    rt_fatal_if(e->focused);\n    rt_assert(ui_app.is_active());\n    rt_assert(ui_app.focused());\n    fp64_t px = ui_app.dpi.monitor_raw / 100.0 + 0.5;\n    e->caret_width = rt_min(3, rt_max(1, (int32_t)px));\n    ui_app.create_caret(e->caret_width, e->fm->height); // w/o line_gap\n    e->focused = true; // means caret was created\n//  rt_println(\"e->focused := true %s\", ui_view_debug_id(&e->view));\n}\n\nstatic void ui_edit_destroy_caret(ui_edit_view_t* e) {\n    rt_fatal_if(!e->focused);\n    ui_app.destroy_caret();\n    e->focused = false; // means caret was destroyed\n//  rt_println(\"e->focused := false %s\", ui_view_debug_id(&e->view));\n}\n\nstatic void ui_edit_show_caret(ui_edit_view_t* e) {\n    if (e->focused) {\n        rt_assert(ui_app.is_active());\n        rt_assert(ui_app.focused());\n        rt_assert((e->caret.x < 0) == (e->caret.y < 0));\n        const ui_ltrb_t insets = ui_view.margins(&e->view, &e->insets);\n        int32_t x = e->caret.x < 0 ? insets.left : e->caret.x;\n        int32_t y = e->caret.y < 0 ? insets.top  : e->caret.y;\n        ui_app.move_caret(e->x + x, e->y + y);\n        // TODO: it is possible to support unblinking caret if desired\n        // do not set blink time - use global default\n//      fatal_if_false(SetCaretBlinkTime(500));\n        ui_app.show_caret();\n        e->shown++;\n        rt_assert(e->shown == 1);\n    }\n}\n\nstatic void ui_edit_hide_caret(ui_edit_view_t* e) {\n    if (e->focused) {\n        ui_app.hide_caret();\n        e->shown--;\n        rt_assert(e->shown == 0);\n    }\n}\n\nstatic void ui_edit_allocate_runs(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(e->para == null);\n    rt_assert(dt->np > 0);\n    rt_assert(e->para == null);\n    bool done = rt_heap.alloc_zero((void**)&e->para,\n                dt->np * sizeof(e->para[0])) == 0;\n    rt_swear(done, \"out of memory - cannot continue\");\n}\n\nstatic void ui_edit_invalidate_run(ui_edit_view_t* e, int32_t i) {\n    if (e->para[i].run != null) {\n        rt_assert(e->para[i].runs > 0);\n        rt_heap.free(e->para[i].run);\n        e->para[i].run = null;\n        e->para[i].runs = 0;\n    } else {\n        rt_assert(e->para[i].runs == 0);\n    }\n}\n\nstatic void ui_edit_invalidate_runs(ui_edit_view_t* e, int32_t f, int32_t t,\n        int32_t np) { // [from..to] inclusive inside [0..np - 1]\n    rt_swear(e->para != null && f <= t && 0 <= f && t < np);\n    for (int32_t i = f; i <= t; i++) { ui_edit_invalidate_run(e, i); }\n}\n\nstatic void ui_edit_invalidate_all_runs(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_invalidate_runs(e, 0, dt->np - 1, dt->np);\n}\n\nstatic void ui_edit_dispose_runs(ui_edit_view_t* e, int32_t np) {\n    rt_assert(e->para != null);\n    ui_edit_invalidate_runs(e, 0, np - 1, np);\n    rt_heap.free(e->para);\n    e->para = null;\n}\n\nstatic void ui_edit_dispose_all_runs(ui_edit_view_t* e) {\n    ui_edit_dispose_runs(e, e->doc->text.np);\n}\n\nstatic void ui_edit_layout_now(ui_edit_view_t* e) {\n    if (e->measure != null && e->layout != null && e->w > 0) {\n        e->layout(&e->view);\n        ui_edit_invalidate_view(e);\n    }\n}\n\nstatic void ui_edit_if_sle_layout(ui_edit_view_t* e) {\n    // only for single line edit controls that were already initialized\n    // and measured horizontally at least once.\n    if (e->sle && e->layout != null && e->w > 0) {\n        ui_edit_layout_now(e);\n    }\n}\n\nstatic void ui_edit_view_set_font(ui_edit_view_t* e, ui_fm_t* f) {\n    ui_edit_invalidate_all_runs(e);\n    e->scroll.rn = 0;\n    e->fm = f;\n    ui_edit_layout_now(e);\n    ui_app.request_layout();\n}\n\n// Paragraph number, glyph number -> run number\n\nstatic ui_edit_pr_t ui_edit_pg_to_pr(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pg.pn];\n    ui_edit_pr_t pr = { .pn = pg.pn, .rn = -1 };\n    if (str->b == 0) { // empty\n        rt_assert(pg.gp == 0);\n        pr.rn = 0;\n    } else {\n        rt_assert(0 <= pg.pn && pg.pn < dt->np);\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pg.pn, &runs);\n        if (pg.gp == str->g + 1) {\n            pr.rn = runs - 1; // TODO: past last glyph ??? is this correct?\n        } else {\n            rt_assert(0 <= pg.gp && pg.gp <= str->g);\n            for (int32_t j = 0; j < runs && pr.rn < 0; j++) {\n                const int32_t last_run = j == runs - 1;\n                const int32_t start = run[j].gp;\n                const int32_t end = run[j].gp + run[j].glyphs + last_run;\n                if (start <= pg.gp && pg.gp < end) {\n                    pr.rn = j;\n                }\n            }\n            rt_assert(pr.rn >= 0);\n        }\n    }\n    return pr;\n}\n\nstatic int32_t ui_edit_runs_between(ui_edit_view_t* e, const ui_edit_pg_t pg0,\n        const ui_edit_pg_t pg1) {\n    rt_assert(ui_edit_range.uint64(pg0) <= ui_edit_range.uint64(pg1));\n    int32_t rn0 = ui_edit_pg_to_pr(e, pg0).rn;\n    int32_t rn1 = ui_edit_pg_to_pr(e, pg1).rn;\n    int32_t rc = 0;\n    if (pg0.pn == pg1.pn) {\n        rt_assert(rn0 <= rn1);\n        rc = rn1 - rn0;\n    } else {\n        rt_assert(pg0.pn < pg1.pn);\n        for (int32_t i = pg0.pn; i < pg1.pn; i++) {\n            const int32_t runs = ui_edit_paragraph_run_count(e, i);\n            if (i == pg0.pn) {\n                rc += runs - rn0;\n            } else { // i < pg1.pn\n                rc += runs;\n            }\n        }\n        rc += rn1;\n    }\n    return rc;\n}\n\nstatic ui_edit_pg_t ui_edit_scroll_pg(ui_edit_view_t* e) {\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, e->scroll.pn, &runs);\n    // layout may decrease number of runs when view is growing:\n    if (e->scroll.rn >= runs) { e->scroll.rn = runs - 1; }\n    rt_assert(0 <= e->scroll.rn && e->scroll.rn < runs,\n            \"e->scroll.rn: %d runs: %d\", e->scroll.rn, runs);\n    return (ui_edit_pg_t) { .pn = e->scroll.pn, .gp = run[e->scroll.rn].gp };\n}\n\nstatic int32_t ui_edit_first_visible_run(ui_edit_view_t* e, int32_t pn) {\n    return pn == e->scroll.pn ? e->scroll.rn : 0;\n}\n\n// ui_edit::pg_to_xy() paragraph # glyph # -> (x,y) in [0,0  width x height]\n\nstatic ui_point_t ui_edit_pg_to_xy(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    ui_point_t pt = { .x = -1, .y = 0 };\n    const int32_t spn = e->scroll.pn + 1;\n    const int32_t pn = rt_min(rt_max(spn, pg.pn + 1), dt->np - 1);\n    for (int32_t i = e->scroll.pn; i <= pn && pt.x < 0; i++) {\n        rt_assert(0 <= i && i < dt->np);\n        const ui_edit_str_t* str = &dt->ps[i];\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, i, &runs);\n        for (int32_t j = ui_edit_first_visible_run(e, i); j < runs; j++) {\n            const int32_t last_run = j == runs - 1;\n            const int32_t gc = run[j].glyphs; // glyphs count\n            if (i == pg.pn) {\n                // in the last `run` of a paragraph x after last glyph is OK\n                if (run[j].gp <= pg.gp && pg.gp < run[j].gp + gc + last_run) {\n                    const char* s = str->u + run[j].bp;\n                    const uint32_t bp2e = str->b - run[j].bp; // to end of str\n                    int32_t ofs = ui_edit_str.gp_to_bp(s, bp2e, pg.gp - run[j].gp);\n                    rt_swear(ofs >= 0);\n                    pt.x = ui_edit_text_width(e, s, ofs);\n                    break;\n                }\n            }\n            pt.y += ui_edit_line_height(e);\n        }\n    }\n    if (0 <= pt.x && pt.x < e->edit.w && 0 <= pt.y && pt.y < e->edit.h) {\n        // all good, inside visible rectangle or right after it\n    } else {\n        rt_println(\"%d:%d (%d,%d) outside of %dx%d\", pg.pn, pg.gp,\n            pt.x, pt.y, e->edit.w, e->edit.h);\n        pt = (ui_point_t){-1, -1};\n    }\n    return pt;\n}\n\nstatic int32_t ui_edit_glyph_width_px(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pg.pn];\n    const char* text = str->u;\n    int32_t gc = str->g;\n    if (pg.gp == 0 &&  gc == 0) {\n        return 0; // empty paragraph\n    } else if (pg.gp < gc) {\n        const int32_t bp = ui_edit_str.gp_to_bp(text, str->b, pg.gp);\n        rt_swear(bp >= 0);\n        const char* s = text + bp;\n        int32_t bytes_in_glyph = rt_str.utf8bytes(s, str->b - bp);\n        rt_swear(bytes_in_glyph > 0);\n        int32_t x = ui_edit_text_width(e, s, bytes_in_glyph);\n        return x;\n    } else {\n        rt_assert(pg.gp == gc, \"only next position past last glyph is allowed\");\n        return 0;\n    }\n}\n\n// xy_to_pg() (x,y) (0,0, width x height) -> paragraph # glyph #\n\nstatic ui_edit_pg_t ui_edit_xy_to_pg(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = {-1, -1};\n    int32_t py = 0; // paragraph `y' coordinate\n    for (int32_t i = e->scroll.pn; i < dt->np && pg.pn < 0; i++) {\n        rt_assert(0 <= i && i < dt->np);\n        const ui_edit_str_t* str = &dt->ps[i];\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, i, &runs);\n        for (int32_t j = ui_edit_first_visible_run(e, i); j < runs && pg.pn < 0; j++) {\n            const ui_edit_run_t* r = &run[j];\n            const char* s = str->u + run[j].bp;\n            if (py <= y && y < py + ui_edit_line_height(e)) {\n                int32_t w = ui_edit_text_width(e, s, r->bytes);\n                pg.pn = i;\n                if (x >= w) {\n                    pg.gp = r->gp + r->glyphs;\n                } else {\n                    pg.gp = r->gp + ui_edit_glyph_at_x(e, i, j, x);\n                    if (pg.gp < r->glyphs - 1) {\n                        ui_edit_pg_t right = {pg.pn, pg.gp + 1};\n                        int32_t x0 = ui_edit_pg_to_xy(e, pg).x;\n                        int32_t x1 = ui_edit_pg_to_xy(e, right).x;\n                        if (x1 - x < x - x0) {\n                            pg.gp++; // snap to closest glyph's 'x'\n                        }\n                    }\n                }\n            } else {\n                py += ui_edit_line_height(e);\n            }\n        }\n        if (py > e->h) { break; }\n    }\n    return pg;\n}\n\nstatic void ui_edit_set_caret(ui_edit_view_t* e, int32_t x, int32_t y) {\n    if (e->caret.x != x || e->caret.y != y) {\n        if (e->focused && ui_app.focused()) {\n            ui_app.move_caret(e->x + x, e->y + y);\n        }\n        const ui_ltrb_t i = ui_view.margins(&e->view, &e->insets);\n        // caret in i.left .. e->view.w - i.right\n        //          i.top  .. e->view.h - i.bottom\n        // coordinate space\n        rt_swear(i.left <= x && x < e->w && i.top <= y && y < e->h);\n        e->caret.x = x;\n        e->caret.y = y;\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_view_end_of_text(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    return (ui_edit_pg_t){ .pn = dt->np - 1, .gp = dt->ps[dt->np - 1].g };\n}\n\nstatic ui_edit_pg_t ui_edit_view_last_fully_visible(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = ui_edit_scroll_pg(e);\n    int32_t visible_runs = e->visible_runs;\n    while (visible_runs > 0) {\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pg.pn, &runs);\n        int32_t i = 0;\n        pg.gp = 0;\n        while (visible_runs > 0 && i < runs) {\n            pg.gp += run[i].glyphs;\n            visible_runs--;\n            i++;\n        }\n        if (visible_runs > 0) {\n            if (pg.pn < dt->np - 1) {\n                pg.pn++;\n                pg.gp = 0;\n            } else {\n                visible_runs = 0; // reached end of text\n            }\n        }\n    }\n    return pg;\n}\n\n// scroll_up() text moves up (north) in the visible view,\n// scroll position increments moves down (south)\n\nstatic void ui_edit_scroll_up(ui_edit_view_t* e, int32_t run_count) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 < run_count, \"does it make sense to have 0 scroll?\");\n    ui_edit_pg_t eot  = ui_edit_view_end_of_text(e);\n    while (run_count > 0) {\n        ui_edit_pg_t lfv = ui_edit_view_last_fully_visible(e);\n        rt_println(\"eot: %d:%d lfv: %d:%d\", eot.pn, eot.gp, lfv.pn, lfv.gp);\n        if (ui_edit_range.compare(lfv, eot) == 0) {\n            run_count = 0;\n        } else {\n            const int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n            if (e->scroll.rn < runs - 1) {\n                e->scroll.rn++;\n                run_count--;\n            } else if (e->scroll.pn < dt->np - 1) {\n                e->scroll.pn++;\n                e->scroll.rn = 0;\n                run_count--;\n            } else {\n                rt_println(\"???\");\n                run_count = 0; // enough\n            }\n            rt_assert(e->scroll.pn >= 0 && e->scroll.rn >= 0);\n        }\n    }\n    ui_edit_if_sle_layout(e);\n    ui_edit_invalidate_view(e);\n}\n\n// scroll_dw() text moves down (south) in the visible view,\n// scroll position decrements moves up (north)\n\nstatic void ui_edit_scroll_down(ui_edit_view_t* e, int32_t run_count) {\n    rt_assert(0 < run_count, \"does it make sense to have 0 scroll?\");\n    while (run_count > 0 && (e->scroll.pn > 0 || e->scroll.rn > 0)) {\n        int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n        e->scroll.rn = rt_min(e->scroll.rn, runs - 1);\n        if (e->scroll.rn == 0 && e->scroll.pn > 0) {\n            e->scroll.pn--;\n            e->scroll.rn = ui_edit_paragraph_run_count(e, e->scroll.pn) - 1;\n        } else if (e->scroll.rn > 0) {\n            e->scroll.rn--;\n        }\n        rt_assert(e->scroll.pn >= 0 && e->scroll.rn >= 0);\n        rt_assert(0 <= e->scroll.rn &&\n                    e->scroll.rn < ui_edit_paragraph_run_count(e, e->scroll.pn));\n        run_count--;\n    }\n    ui_edit_if_sle_layout(e);\n    ui_edit_invalidate_view(e);\n}\n\nstatic void ui_edit_scroll_into_view(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np && dt->np > 0);\n    if (e->inside.bottom > 0) {\n        if (e->sle) { rt_assert(pg.pn == 0); }\n        const int32_t rn = ui_edit_pg_to_pr(e, pg).rn;\n        const uint64_t scroll = (uint64_t)e->scroll.pn << 32 | e->scroll.rn;\n        const uint64_t caret  = (uint64_t)pg.pn << 32 | rn;\n        uint64_t last = 0;\n        int32_t py = 0;\n        const int32_t pn = e->scroll.pn;\n        const int32_t bottom = e->inside.bottom;\n        for (int32_t i = pn; i < dt->np && py < bottom; i++) {\n            int32_t runs = ui_edit_paragraph_run_count(e, i);\n            const int32_t fvr = ui_edit_first_visible_run(e, i);\n            for (int32_t j = fvr; j < runs && py < bottom; j++) {\n                last = (uint64_t)i << 32 | j;\n                py += ui_edit_line_height(e);\n            }\n        }\n        int32_t sle_runs = e->sle && e->w > 0 ?\n            ui_edit_paragraph_run_count(e, 0) : 0;\n        ui_edit_pg_t end = ui_edit_text.end(dt);\n        ui_edit_pr_t lp = ui_edit_pg_to_pr(e, end);\n        uint64_t eof = (uint64_t)(dt->np - 1) << 32 | lp.rn;\n        if (last == eof && py <= bottom - ui_edit_line_height(e)) {\n            // vertical white space for EOF on the screen\n            last = (uint64_t)dt->np << 32 | 0;\n        }\n        if (scroll <= caret && caret < last) {\n            // no scroll\n        } else if (caret < scroll) {\n            ui_edit_invalidate_view(e);\n            e->scroll.pn = pg.pn;\n            e->scroll.rn = rn;\n        } else if (e->sle && sle_runs * ui_edit_line_height(e) <= e->h) {\n            // single line edit control fits vertically - no scroll\n        } else {\n            ui_edit_invalidate_view(e);\n            rt_assert(caret >= last);\n            e->scroll.pn = pg.pn;\n            e->scroll.rn = rn;\n            while (e->scroll.pn > 0 || e->scroll.rn > 0) {\n                ui_point_t pt = ui_edit_pg_to_xy(e, pg);\n                if (pt.y + ui_edit_line_height(e) > bottom - ui_edit_line_height(e)) { break; }\n                if (e->scroll.rn > 0) {\n                    e->scroll.rn--;\n                } else {\n                    e->scroll.pn--;\n                    e->scroll.rn = ui_edit_paragraph_run_count(e, e->scroll.pn) - 1;\n                }\n            }\n        }\n    }\n}\n\nstatic void ui_edit_caret_to(ui_edit_view_t* e, const ui_edit_pg_t to) {\n    ui_edit_scroll_into_view(e, to);\n    ui_point_t pt =  ui_edit_pg_to_xy(e, to);\n    if (pt.x >= 0 && pt.y >= 0) {\n        ui_edit_set_caret(e, pt.x + e->inside.left, pt.y + e->inside.top);\n    }\n}\n\nstatic void ui_edit_move_caret(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    if (e->w > 0) { // width == 0 means no measure/layout yet\n        ui_rect_t before = ui_edit_selection_rect(e);\n        ui_edit_text_t* dt = &e->doc->text; // document text\n        rt_assert(0 <= pg.pn && pg.pn < dt->np);\n        // single line edit control cannot move caret past fist paragraph\n        if (!e->sle || pg.pn < dt->np) {\n            e->selection.a[1] = pg;\n            ui_edit_caret_to(e, pg);\n            if (!ui_app.shift && e->edit.buttons == 0) {\n                e->selection.a[0] = e->selection.a[1];\n            }\n        }\n        ui_rect_t after = ui_edit_selection_rect(e);\n        ui_edit_invalidate_rect(e, ui.combine_rect(&before, &after));\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_insert_inline(ui_edit_view_t* e, ui_edit_pg_t pg,\n        const char* text, int32_t bytes) {\n    // insert_inline() inserts text (not containing '\\n' in it)\n    rt_assert(bytes > 0);\n    for (int32_t i = 0; i < bytes; i++) { rt_assert(text[i] != '\\n'); }\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    int32_t g = 0;\n    if (ui_edit_doc.replace(e->doc, &r, text, bytes)) {\n        ui_edit_text_t t = {0};\n        if (ui_edit_text.init(&t, text, bytes, false)) {\n            rt_assert(t.ps != null && t.np == 1);\n            g = t.np == 1 && t.ps != null ? t.ps[0].g : 0;\n            ui_edit_text.dispose(&t);\n        }\n    }\n    r.from.gp += g;\n    r.to.gp += g;\n    e->selection = r;\n    ui_edit_move_caret(e, e->selection.from);\n    return r.to;\n}\n\nstatic ui_edit_pg_t ui_edit_insert_paragraph_break(ui_edit_view_t* e,\n        ui_edit_pg_t pg) {\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    bool ok = ui_edit_doc.replace(e->doc, &r, \"\\n\", 1);\n    ui_edit_pg_t next = {.pn = pg.pn + 1, .gp = 0};\n    return ok ? next : pg;\n}\n\nstatic bool ui_edit_is_blank(ui_edit_glyph_t g) {\n    return g.bytes == 0 || ui_edit_str.is_blank(rt_str.utf32(g.s, g.bytes));\n}\n\nstatic bool ui_edit_is_punctuation(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 && ui_edit_str.is_punctuation(utf32);\n}\n\nstatic bool ui_edit_is_alphanumeric(ui_edit_glyph_t g) {\n    return g.bytes > 0 &&\n        ui_edit_str.is_alphanumeric(rt_str.utf32(g.s, g.bytes));\n}\n\nstatic bool ui_edit_is_cjk_or_emoji_or_symbol(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 &&\n        (ui_edit_str.is_cjk_or_emoji(utf32) || ui_edit_str.is_symbol(utf32));\n}\n\nstatic bool ui_edit_is_break(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 &&\n       (ui_edit_str.is_blank(utf32) ||\n        ui_edit_str.is_punctuation(utf32) ||\n        ui_edit_str.is_symbol(utf32) ||\n        ui_edit_str.is_cjk_or_emoji(utf32));\n}\n\nstatic ui_edit_glyph_t ui_edit_left_of(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    if (pg.gp > 0) {\n        pg.gp--;\n        return ui_edit_glyph_at(e, pg);\n    } else {\n        return (ui_edit_glyph_t){ null, 0 };\n    }\n}\n\nstatic ui_edit_glyph_t ui_edit_right_of(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (pg.gp < dt->ps[pg.pn].g - 1) {\n        pg.gp++;\n        return ui_edit_glyph_at(e, pg);\n    } else {\n        return (ui_edit_glyph_t){ null, 0 };\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_skip_left_blanks(ui_edit_view_t* e,\n    ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_swear(pg.pn <= dt->np - 1);\n    while (pg.gp > 0) {\n        pg.gp--;\n        ui_edit_glyph_t glyph = ui_edit_glyph_at(e, pg);\n        if (glyph.bytes > 0 && !ui_edit_is_blank(glyph)) {\n            pg.gp++;\n            break;\n        }\n    }\n    return pg;\n}\n\nstatic ui_edit_pg_t ui_edit_skip_right_blanks(ui_edit_view_t* e,\n    ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_swear(pg.pn <= dt->np - 1);\n    int32_t glyphs = ui_edit_glyphs_in_paragraph(e, pg.pn);\n    ui_edit_glyph_t glyph = ui_edit_glyph_at(e, pg);\n    while (pg.gp < glyphs && glyph.bytes > 0 && ui_edit_is_blank(glyph)) {\n        pg.gp++;\n        glyph = ui_edit_glyph_at(e, pg);\n    }\n    return pg;\n}\n\nstatic ui_edit_range_t ui_edit_word_range(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (0 <= pg.pn && 0 <= pg.gp) {\n        rt_swear(pg.pn <= dt->np - 1);\n        // number of glyphs in paragraph:\n        int32_t ng = ui_edit_glyphs_in_paragraph(e, pg.pn);\n        if (pg.gp > ng) { pg.gp = rt_max(0, ng); }\n        ui_edit_glyph_t g = ui_edit_glyph_at(e, pg);\n        if (ng <= 1) {\n            r.to.gp = ng;\n        } else if (ui_edit_is_cjk_or_emoji_or_symbol(g)) {\n            // r == {pg,pg}\n        } else {\n            ui_edit_pg_t from = pg;\n            ui_edit_pg_t to   = pg;\n            if (pg.gp > 0 && ui_edit_is_punctuation(g)) {\n                from.gp--;\n                g = ui_edit_glyph_at(e, from);\n            } else if (pg.gp > 0 && ui_edit_is_blank(g)) {\n                from.gp--;\n                to.gp--;\n                g = ui_edit_glyph_at(e, from);\n            }\n            if (ui_edit_is_blank(g)) {\n                while (from.gp > 0 &&\n                       ui_edit_is_blank(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_blank(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            } else if (ui_edit_is_alphanumeric(g)) {\n                while (from.gp > 0 &&\n                       ui_edit_is_alphanumeric(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_alphanumeric(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            } else {\n                while (from.gp > 0 &&\n                        ui_edit_is_break(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_break(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            }\n        }\n    }\n    return r;\n}\n\nstatic void ui_edit_ctrl_left(ui_edit_view_t* e) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    const ui_edit_range_t s = e->selection;\n    ui_edit_pg_t to = e->selection.to;\n    if (to.gp == 0) {\n        if (to.pn > 0) {\n            to.pn--;\n            int32_t runs = 0;\n            const ui_edit_run_t* run = ui_edit_paragraph_runs(e, to.pn, &runs);\n            to.gp = run[runs - 1].gp + run[runs - 1].glyphs;\n        }\n    } else {\n        to.gp--;\n    }\n    const ui_edit_pg_t lf = ui_edit_skip_left_blanks(e, to);\n    const ui_edit_range_t w = ui_edit_word_range(e, lf);\n    e->selection.to = w.from;\n    if (ui_app.shift) {\n        e->selection.from = s.from;\n    } else {\n        e->selection.from = w.from;\n    }\n    ui_edit_move_caret(e, e->selection.to);\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n}\n\nstatic void ui_edit_view_key_left(ui_edit_view_t* e) {\n    ui_edit_pg_t to = e->selection.a[1];\n    if (to.pn > 0 || to.gp > 0) {\n        if (ui_app.ctrl) {\n            ui_edit_ctrl_left(e);\n        } else {\n            ui_point_t pt = ui_edit_pg_to_xy(e, to);\n            if (pt.x == 0 && pt.y == 0) {\n                ui_edit_scroll_down(e, 1);\n            }\n            if (to.gp > 0) {\n                to.gp--;\n            } else if (to.pn > 0) {\n                to.pn--;\n                to.gp = ui_edit_glyphs_in_paragraph(e, to.pn);\n            }\n            ui_edit_move_caret(e, to);\n            e->last_x = -1;\n        }\n    }\n}\n\nstatic void ui_edit_ctrl_right(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_range_t s = e->selection;\n    ui_edit_pg_t to = e->selection.to;\n    int32_t glyphs = ui_edit_glyphs_in_paragraph(e, to.pn);\n    if (to.pn < dt->np - 1 || to.gp < glyphs) {\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        if (to.gp == glyphs) {\n            to.pn++;\n            to.gp = 0;\n        } else {\n            to.gp++;\n        }\n        ui_edit_pg_t rt = ui_edit_skip_right_blanks(e, to);\n        ui_edit_range_t w = ui_edit_word_range(e, rt);\n        e->selection.to = w.to;\n        if (ui_app.shift) {\n            e->selection.from = s.from;\n        } else {\n            e->selection.from = w.to;\n        }\n        ui_edit_move_caret(e, e->selection.to);\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    }\n}\n\nstatic void ui_edit_view_key_right(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t to = e->selection.a[1];\n    if (to.pn < dt->np) {\n        if (ui_app.ctrl) {\n            ui_edit_ctrl_right(e);\n        } else {\n            int32_t glyphs = ui_edit_glyphs_in_paragraph(e, to.pn);\n            if (to.gp < glyphs) {\n                to.gp++;\n                ui_edit_scroll_into_view(e, to);\n            } else if (!e->sle && to.pn < dt->np - 1) {\n                to.pn++;\n                to.gp = 0;\n                ui_edit_scroll_into_view(e, to);\n            }\n            ui_edit_move_caret(e, to);\n// TODO: last_x does not work!\n            e->last_x = -1;\n        }\n    }\n}\n\nstatic void ui_edit_reuse_last_x(ui_edit_view_t* e, ui_point_t* pt) {\n    // Vertical caret movement visually tend to move caret horizontally\n    // in proportional font text. Remembering starting `x' value for vertical\n    // movements alleviates this unpleasant UX experience to some degree.\n    if (pt->x > 0) {\n        if (e->last_x > 0) {\n            int32_t prev = e->last_x - e->fm->em.w;\n            int32_t next = e->last_x + e->fm->em.w;\n            if (prev <= pt->x && pt->x <= next) {\n                pt->x = e->last_x;\n            }\n        }\n        e->last_x = pt->x;\n    }\n}\n\nstatic void ui_edit_view_key_up(ui_edit_view_t* e) {\n    const ui_edit_pg_t pg = e->selection.a[1];\n    ui_edit_pg_t to = pg;\n    if (to.pn > 0 || ui_edit_pg_to_pr(e, to).rn > 0) {\n        // top of the text\n        ui_point_t pt = ui_edit_pg_to_xy(e, to);\n        rt_assert(pt.x >= 0 && pt.y >= 0);\n        if (pt.y == 0) {\n            ui_edit_scroll_down(e, 1);\n        } else {\n            pt.y -= 1;\n        }\n        ui_edit_reuse_last_x(e, &pt);\n        rt_assert(pt.y >= 0);\n        to = ui_edit_xy_to_pg(e, pt.x, pt.y);\n        if (to.pn >= 0 && to.gp >= 0) {\n            int32_t rn0 = ui_edit_pg_to_pr(e, pg).rn;\n            int32_t rn1 = ui_edit_pg_to_pr(e, to).rn;\n            if (rn1 > 0 && rn0 == rn1) { // same run\n                rt_assert(to.gp > 0, \"word break must not break on zero gp\");\n                int32_t runs = 0;\n                const ui_edit_run_t* run = ui_edit_paragraph_runs(e, to.pn, &runs);\n                to.gp = run[rn1].gp;\n            }\n        }\n    }\n    if (to.pn >= 0 && to.gp >= 0) {\n        ui_edit_move_caret(e, to);\n    }\n}\n\nstatic void ui_edit_view_key_down(ui_edit_view_t* e) {\n    const ui_edit_pg_t pg = e->selection.a[1];\n    ui_point_t pt = ui_edit_pg_to_xy(e, pg);\n    ui_edit_reuse_last_x(e, &pt); // TODO: does not work! (used to work broken now)\n    // scroll runs guaranteed to be already laid out for current state of view:\n    ui_edit_pg_t scroll = ui_edit_scroll_pg(e);\n    const int32_t run_count = ui_edit_runs_between(e, scroll, pg);\n    if (!e->sle && run_count > e->visible_runs - 1) {\n        ui_edit_scroll_up(e, 1);\n    } else {\n        pt.y += ui_edit_line_height(e);\n    }\n    ui_edit_pg_t to = ui_edit_xy_to_pg(e, pt.x, pt.y);\n    if (to.pn >= 0 && to.gp >= 0) {\n        ui_edit_move_caret(e, to);\n    }\n}\n\nstatic void ui_edit_view_key_home(ui_edit_view_t* e) {\n    if (ui_app.ctrl) {\n        e->scroll.pn = 0;\n        e->scroll.rn = 0;\n        e->selection.a[1].pn = 0;\n        e->selection.a[1].gp = 0;\n        ui_edit_invalidate_view(e);\n    }\n    const int32_t pn = e->selection.a[1].pn;\n    int32_t runs = ui_edit_paragraph_run_count(e, pn);\n    const ui_edit_paragraph_t* para = &e->para[pn];\n    if (runs <= 1) {\n        e->selection.a[1].gp = 0;\n    } else {\n        int32_t rn = ui_edit_pg_to_pr(e, e->selection.a[1]).rn;\n        rt_assert(0 <= rn && rn < runs);\n        const int32_t gp = para->run[rn].gp;\n        if (e->selection.a[1].gp != gp) {\n            // first Home keystroke moves caret to start of run\n            e->selection.a[1].gp = gp;\n        } else {\n            // second Home keystroke moves caret start of paragraph\n            e->selection.a[1].gp = 0;\n            if (e->scroll.pn >= e->selection.a[1].pn) { // scroll in\n                e->scroll.pn = e->selection.a[1].pn;\n                e->scroll.rn = 0;\n                ui_edit_invalidate_view(e);\n            }\n        }\n    }\n    if (!ui_app.shift) {\n        e->selection.a[0] = e->selection.a[1];\n    }\n    ui_edit_move_caret(e, e->selection.a[1]);\n}\n\nstatic void ui_edit_view_key_eol(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    int32_t pn = e->selection.a[1].pn;\n    int32_t gp = e->selection.a[1].gp;\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pn, &runs);\n    int32_t rn = ui_edit_pg_to_pr(e, e->selection.a[1]).rn;\n    rt_assert(0 <= rn && rn < runs);\n    if (rn == runs - 1) {\n        e->selection.a[1].gp = str->g;\n    } else if (e->selection.a[1].gp == str->g) {\n        // at the end of paragraph do nothing (or move caret to EOF?)\n    } else if (str->g > 0 && gp != run[rn].glyphs - 1) {\n        e->selection.a[1].gp = run[rn].gp + run[rn].glyphs - 1;\n    } else {\n        e->selection.a[1].gp = str->g;\n    }\n}\n\nstatic void ui_edit_view_key_end(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    if (ui_app.ctrl) {\n        int32_t py = e->inside.bottom;\n        for (int32_t i = dt->np - 1; i >= 0 && py >= ui_edit_line_height(e); i--) {\n            int32_t runs = ui_edit_paragraph_run_count(e, i);\n            for (int32_t j = runs - 1; j >= 0 && py >= ui_edit_line_height(e); j--) {\n                py -= ui_edit_line_height(e);\n                if (py < ui_edit_line_height(e)) {\n                    e->scroll.pn = i;\n                    e->scroll.rn = j;\n                }\n            }\n        }\n        e->selection.a[1] = ui_edit_text.end(dt);\n        ui_edit_invalidate_view(e);\n    } else {\n        ui_edit_view_key_eol(e);\n    }\n    if (!ui_app.shift) {\n        e->selection.a[0] = e->selection.a[1];\n    }\n    ui_edit_move_caret(e, e->selection.a[1]);\n}\n\nstatic void ui_edit_view_key_page_up(ui_edit_view_t* e) {\n    int32_t n = rt_max(1, e->visible_runs - 1);\n    ui_edit_pg_t scr = ui_edit_scroll_pg(e);\n    const ui_edit_pg_t prev = (ui_edit_pg_t){\n        .pn = rt_max(scr.pn - e->visible_runs - 1, 0),\n        .gp = 0\n    };\n    const int32_t m = ui_edit_runs_between(e, prev, scr);\n    if (m > n) {\n        ui_point_t pt = ui_edit_pg_to_xy(e, e->selection.a[1]);\n        ui_edit_pr_t scroll = e->scroll;\n        ui_edit_scroll_down(e, n);\n        if (scroll.pn != e->scroll.pn || scroll.rn != e->scroll.rn) {\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, pt.x, pt.y);\n            ui_edit_move_caret(e, pg);\n        }\n    } else {\n        const ui_edit_pg_t bof = {.pn = 0, .gp = 0};\n        ui_edit_move_caret(e, bof);\n    }\n}\n\nstatic void ui_edit_view_key_page_down(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    const int32_t n = rt_max(1, e->visible_runs - 1);\n    const ui_edit_pg_t scr = ui_edit_scroll_pg(e);\n    const ui_edit_pg_t next = (ui_edit_pg_t){\n        .pn = rt_min(scr.pn + 1, dt->np - 1),\n        .gp = scr.pn + 1 == dt->np - 1 ? dt->ps[dt->np - 1].g : 0\n    };\n    const int32_t m = ui_edit_runs_between(e, scr, next);\n    if (m > n) {\n        const ui_point_t pt = ui_edit_pg_to_xy(e, e->selection.a[1]);\n        const ui_edit_pr_t scroll = e->scroll;\n        ui_edit_scroll_up(e, n);\n        if (scroll.pn != e->scroll.pn || scroll.rn != e->scroll.rn) {\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, pt.x, pt.y);\n            ui_edit_move_caret(e, pg);\n        }\n    } else {\n        const ui_edit_pg_t end = ui_edit_text.end(dt);\n        ui_edit_move_caret(e, end);\n    }\n}\n\nstatic void ui_edit_view_key_delete(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    uint64_t f = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t t = ui_edit_range.uint64(e->selection.a[1]);\n    uint64_t end = ui_edit_range.uint64(ui_edit_text.end(dt));\n    if (f == t && t != end) {\n        ui_edit_pg_t s1 = e->selection.a[1];\n        ui_edit_view.key_right(e);\n        e->selection.a[1] = s1;\n    }\n    ui_edit_view.erase(e);\n}\n\nstatic void ui_edit_view_key_backspace(ui_edit_view_t* e) {\n    uint64_t f = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t t = ui_edit_range.uint64(e->selection.a[1]);\n    if (t != 0 && f == t) {\n        ui_edit_pg_t s1 = e->selection.a[1];\n        ui_edit_view.key_left(e);\n        e->selection.a[1] = s1;\n    }\n    ui_edit_view.erase(e);\n}\n\nstatic void ui_edit_view_key_enter(ui_edit_view_t* e) {\n    rt_assert(!e->ro);\n    if (!e->sle) {\n        ui_edit_view.erase(e);\n        e->selection.a[1] = ui_edit_insert_paragraph_break(e, e->selection.a[1]);\n        e->selection.a[0] = e->selection.a[1];\n        ui_edit_move_caret(e, e->selection.a[1]);\n    } else { // single line edit callback\n        if (ui_edit_view.enter != null) { ui_edit_view.enter(e); }\n    }\n}\n\nstatic bool ui_edit_view_key_pressed(ui_view_t* v, int64_t key) {\n    bool swallow = false;\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (e->focused) {\n        swallow = true;\n        if (key == ui.key.down && e->selection.a[1].pn < dt->np) {\n            ui_edit_view.key_down(e);\n        } else if (key == ui.key.up && dt->np > 1) {\n            ui_edit_view.key_up(e);\n        } else if (key == ui.key.left) {\n            ui_edit_view.key_left(e);\n        } else if (key == ui.key.right) {\n            ui_edit_view.key_right(e);\n        } else if (key == ui.key.page_up) {\n            ui_edit_view.key_page_up(e);\n        } else if (key == ui.key.page_down) {\n            ui_edit_view.key_page_down(e);\n        } else if (key == ui.key.home) {\n            ui_edit_view.key_home(e);\n        } else if (key == ui.key.end) {\n            ui_edit_view.key_end(e);\n        } else if (key == ui.key.del && !e->ro) {\n            ui_edit_view.key_delete(e);\n        } else if (key == ui.key.back && !e->ro) {\n            ui_edit_view.key_backspace(e);\n        } else if (key == ui.key.enter && !e->ro) {\n            ui_edit_view.key_enter(e);\n        } else {\n            swallow = false; // ignore other keys\n        }\n    }\n    return swallow;\n}\n\nstatic void ui_edit_undo(ui_edit_view_t* e) {\n    if (e->doc->undo != null) {\n        ui_edit_doc.undo(e->doc);\n    } else {\n        ui_app.beep(ui.beep.error);\n    }\n}\nstatic void ui_edit_redo(ui_edit_view_t* e) {\n    if (e->doc->redo != null) {\n        ui_edit_doc.redo(e->doc);\n    } else {\n        ui_app.beep(ui.beep.error);\n    }\n}\n\nstatic void ui_edit_character(ui_view_t* v, const char* utf8) {\n    rt_assert(v->type == ui_view_text);\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    #pragma push_macro(\"ui_edit_ctrl\")\n    #define ui_edit_ctrl(c) ((char)((c) - 'a' + 1))\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (e->focused) {\n        char ch = utf8[0];\n        if (ui_app.ctrl) {\n            if (ch == ui_edit_ctrl('a')) { ui_edit_view.select_all(e); }\n            if (ch == ui_edit_ctrl('c')) { ui_edit_view.copy(e); }\n            if (!e->ro) {\n                if (ch == ui_edit_ctrl('x')) { ui_edit_view.cut(e); }\n                if (ch == ui_edit_ctrl('v')) { ui_edit_view.paste(e); }\n                if (ch == ui_edit_ctrl('y')) { ui_edit_redo(e); }\n                if (ch == ui_edit_ctrl('z') || ch == ui_edit_ctrl('Z')) {\n                    if (ui_app.shift) { // Ctrl+Shift+Z\n                        ui_edit_redo(e);\n                    } else { // Ctrl+Z\n                        ui_edit_undo(e);\n                    }\n                }\n            }\n        }\n        if (0x20u <= (uint8_t)ch && !e->ro) { // 0x20 space\n            int32_t len = (int32_t)strlen(utf8);\n            int32_t bytes = rt_str.utf8bytes(utf8, len);\n            if (bytes > 0) {\n                ui_edit_view.erase(e); // remove selected text to be replaced by glyph\n                e->selection.a[1] = ui_edit_insert_inline(e,\n                    e->selection.a[1], utf8, bytes);\n                e->selection.a[0] = e->selection.a[1];\n                ui_edit_move_caret(e, e->selection.a[1]);\n            } else {\n                rt_println(\"invalid UTF8: 0x%02X%02X%02X%02X\",\n                        utf8[0], utf8[1], utf8[2], utf8[3]);\n            }\n        }\n    }\n    #pragma pop_macro(\"ui_edit_ctrl\")\n}\n\nstatic void ui_edit_select_word(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= pg.pn && 0 <= pg.gp) {\n        ui_edit_range_t r = ui_edit_word_range(e, pg);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, r.to.pn);\n        if (r.to.pn == r.from.pn && r.to.gp == r.from.gp && r.to.gp < glyphs) {\n            r.to.gp++; // at least one glyph to the right\n        }\n        if (ui_edit_range.compare(r.from, pg) != 0 ||\n            ui_edit_range.compare(r.to, pg) != 0) {\n            e->selection = r;\n            ui_edit_caret_to(e, r.to);\n//          rt_println(\"e->selection.a[1] = %d.%d\", to.pn, to.gp);\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n            e->edit.buttons = 0;\n        }\n    }\n}\n\nstatic void ui_edit_select_paragraph(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t p = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= p.pn && 0 <= p.gp) {\n        ui_edit_range_t r = ui_edit_text.ordered(dt, &e->selection);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, p.pn);\n        if (p.gp > glyphs) { p.gp = rt_max(0, glyphs); }\n        if (p.pn == r.a[0].pn && r.a[0].pn == r.a[1].pn &&\n            r.a[0].gp <= p.gp && p.gp <= r.a[1].gp) {\n            r.a[0].gp = 0;\n            if (p.pn < dt->np - 1) {\n                r.a[1].pn = p.pn + 1;\n                r.a[1].gp = 0;\n            } else {\n                r.a[1].gp = dt->ps[p.pn].g;\n            }\n            e->selection = r;\n            ui_edit_caret_to(e, r.to);\n        }\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        e->edit.buttons = 0;\n    }\n}\n\nstatic void ui_edit_click(ui_edit_view_t* e, int32_t x, int32_t y) {\n    // x, y in 0..e->w, 0->e.h coordinate space\n    rt_assert(0 <= x && x < e->w && 0 <= y && y < e->h);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= pg.pn && 0 <= pg.gp && ui_view.has_focus(&e->view)) {\n        rt_swear(dt->np > 0 && pg.pn < dt->np);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, pg.pn);\n        if (pg.gp > glyphs) { pg.gp = rt_max(0, glyphs); }\n        ui_edit_move_caret(e, pg);\n    }\n}\n\nstatic void ui_edit_mouse_button_down(ui_edit_view_t* e, int32_t ix) {\n    e->edit.buttons |= (1 << ix);\n}\n\nstatic void ui_edit_mouse_button_up(ui_edit_view_t* e, int32_t ix) {\n    e->edit.buttons &= ~(1 << ix);\n}\n\nstatic bool ui_edit_tap(ui_view_t* v, int32_t rt_unused(ix), bool pressed) {\n    // `ix` ignored for now till context menu (copy/paste/select...)\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    // not just inside view but inside insets:\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside) {\n        if (pressed) {\n            e->edit.buttons = 0;\n            ui_edit_click(e, x, y);\n            ui_edit_mouse_button_down(e, ix);\n        } else if (!pressed) {\n            ui_edit_mouse_button_up(e, ix);\n        }\n    }\n    if (!pressed) { ui_edit_mouse_button_up(e, ix); }\n    return true;\n}\n\nstatic bool ui_edit_long_press(ui_view_t* v, int32_t rt_unused(ix)) {\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside && ui_edit_range.is_empty(e->selection)) {\n        ui_edit_select_paragraph(e, x, y);\n    }\n    return true;\n}\n\nstatic bool ui_edit_double_tap(ui_view_t* v, int32_t rt_unused(ix)) {\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside && e->selection.a[0].pn == e->selection.a[1].pn) {\n        ui_edit_select_word(e, x, y);\n    }\n    return false;\n}\n\nstatic void ui_edit_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    if (v->w > 0 && v->h > 0) {\n        const int32_t dy = dx_dy.y;\n        // TODO: maybe make a use of dx in single line no-word-break edit control?\n        if (ui_app.focus == v) {\n            rt_assert(v->type == ui_view_text);\n            ui_edit_view_t* e = (ui_edit_view_t*)v;\n            int32_t lines = (abs(dy) + ui_edit_line_height(e) - 1) / ui_edit_line_height(e);\n            if (dy > 0) {\n                ui_edit_scroll_down(e, lines);\n            } else if (dy < 0) {\n                ui_edit_scroll_up(e, lines);\n            }\n//  TODO: Ctrl UP/DW and caret of out of visible area scrolls are not\n//        implemented. Not sure they are very good UX experience.\n//        MacOS users may be used to scroll with touchpad, take a visual\n//        peek, do NOT click and continue editing at last cursor position.\n//        To me back forward stack navigation is much more intuitive and\n//        much mode \"modeless\" in spirit of cut/copy/paste. But opinions\n//        and editing habits vary. Easy to implement.\n            const int32_t x = e->caret.x - e->inside.left;\n            const int32_t y = e->caret.y - e->inside.top;\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n            if (pg.pn >= 0 && pg.gp >= 0) {\n                rt_assert(pg.gp <= e->doc->text.ps[pg.pn].g);\n                ui_edit_move_caret(e, pg);\n            } else {\n                ui_edit_click(e, x, y);\n            }\n        }\n    }\n}\n\nstatic bool ui_edit_focus_gained(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    rt_assert(v->focusable);\n    if (ui_app.focused() && !e->focused) {\n        ui_edit_create_caret(e);\n        ui_edit_show_caret(e);\n        ui_edit_if_sle_layout(e);\n    }\n    e->edit.buttons = 0;\n    ui_app.request_redraw();\n    return true;\n}\n\nstatic void ui_edit_focus_lost(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (e->focused) {\n        ui_edit_hide_caret(e);\n        ui_edit_destroy_caret(e);\n        ui_edit_if_sle_layout(e);\n    }\n    e->edit.buttons = 0;\n    ui_app.request_redraw();\n}\n\nstatic void ui_edit_view_erase(ui_edit_view_t* e) {\n    if (e->selection.from.pn != e->selection.to.pn) {\n        ui_edit_invalidate_view(e);\n    } else {\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    }\n    ui_edit_range_t r = ui_edit_range.order(e->selection);\n    if (!ui_edit_range.is_empty(r) && ui_edit_doc.replace(e->doc, &r, null, 0)) {\n        e->selection = r;\n        e->selection.to = e->selection.from;\n        ui_edit_move_caret(e, e->selection.from);\n    }\n}\n\nstatic void ui_edit_select_all(ui_edit_view_t* e) {\n    e->selection = ui_edit_text.all_on_null(&e->doc->text, null);\n    ui_edit_invalidate_view(e);\n}\n\nstatic int32_t ui_edit_view_save(ui_edit_view_t* e, char* text, int32_t* bytes) {\n    rt_not_null(bytes);\n    enum {\n        error_insufficient_buffer = 122, // ERROR_INSUFFICIENT_BUFFER\n        error_more_data = 234            // ERROR_MORE_DATA\n    };\n    int32_t r = 0;\n    const int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, null);\n    if (text == null) {\n        *bytes = utf8bytes;\n        r = rt_core.error.more_data;\n    } else if (*bytes < utf8bytes) {\n        r = rt_core.error.insufficient_buffer;\n    } else {\n        ui_edit_doc.copy(e->doc, null, text, utf8bytes);\n        rt_assert(text[utf8bytes - 1] == 0x00);\n    }\n    return r;\n}\n\nstatic void ui_edit_view_copy(ui_edit_view_t* e) {\n    int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, &e->selection);\n    if (utf8bytes > 0) {\n        char* text = null;\n        bool ok = rt_heap.alloc((void**)&text, utf8bytes) == 0;\n        rt_swear(ok);\n        ui_edit_doc.copy(e->doc, &e->selection, text, utf8bytes);\n        rt_assert(text[utf8bytes - 1] == 0x00); // verify zero termination\n        rt_clipboard.put_text(text);\n        rt_heap.free(text);\n        static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n        int32_t x = e->x + e->caret.x;\n        int32_t y = e->y + e->caret.y - ui_edit_line_height(e);\n        if (y < ui_app.content->y) {\n            y += ui_edit_line_height(e) * 2;\n        }\n        if (y > ui_app.content->y + ui_app.content->h - ui_edit_line_height(e)) {\n            y = e->caret.y;\n        }\n        ui_app.show_hint(&hint, x, y, 0.5);\n    }\n}\n\nstatic void ui_edit_view_cut(ui_edit_view_t* e) {\n    int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, &e->selection);\n    if (utf8bytes > 0) { ui_edit_view_copy(e); }\n    if (!e->ro) { ui_edit_view.erase(e); }\n}\n\nstatic ui_edit_pg_t ui_edit_paste_text(ui_edit_view_t* e,\n        const char* text, int32_t bytes) {\n    rt_assert(!e->ro);\n    ui_edit_text_t t = {0};\n    ui_edit_text.init(&t, text, bytes, false);\n    ui_edit_range_t r = ui_edit_text.all_on_null(&t, null);\n    ui_edit_doc.replace(e->doc, &e->selection, text, bytes);\n    ui_edit_pg_t pg = e->selection.from;\n    pg.pn += r.to.pn;\n    if (e->selection.from.pn == e->selection.to.pn && r.to.pn == 0) {\n        pg.gp = e->selection.from.gp + r.to.gp;\n    } else {\n        pg.gp = r.to.gp;\n    }\n    ui_edit_text.dispose(&t);\n    return pg;\n}\n\nstatic void ui_edit_view_replace(ui_edit_view_t* e, const char* s, int32_t n) {\n    if (!e->ro) {\n        if (n < 0) { n = (int32_t)strlen(s); }\n        ui_edit_view.erase(e);\n        e->selection.a[1] = ui_edit_paste_text(e, s, n);\n        e->selection.a[0] = e->selection.a[1];\n        if (e->w > 0) { ui_edit_move_caret(e, e->selection.a[1]); }\n    }\n}\n\nstatic void ui_edit_view_paste(ui_edit_view_t* e) {\n    if (!e->ro) {\n        ui_edit_pg_t pg = e->selection.a[1];\n        int32_t bytes = 0;\n        rt_clipboard.get_text(null, &bytes);\n        if (bytes > 0) {\n            char* text = null;\n            bool ok = rt_heap.alloc((void**)&text, bytes) == 0;\n            rt_swear(ok);\n            int32_t r = rt_clipboard.get_text(text, &bytes);\n            rt_fatal_if_error(r);\n            if (bytes > 0 && text[bytes - 1] == 0) {\n                bytes--; // clipboard includes zero terminator\n            }\n            if (bytes > 0) {\n                ui_edit_view.erase(e);\n                pg = ui_edit_paste_text(e, text, bytes);\n                ui_edit_move_caret(e, pg);\n            }\n            rt_heap.free(text);\n        }\n    }\n}\n\nstatic void ui_edit_prepare_sle(ui_edit_view_t* e) {\n    ui_view_t* v = &e->view;\n    rt_swear(e->sle && v->w > 0);\n    // shingle line edit is capable of resizing itself to two\n    // lines of text (and shrinking back) to avoid horizontal scroll\n    int32_t runs = rt_max(1, rt_min(2, ui_edit_paragraph_run_count(e, 0)));\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    int32_t h = insets.top + ui_edit_line_height(e) * runs + insets.bottom;\n    fp32_t min_h_em = (fp32_t)h / v->fm->em.h;\n    if (v->min_h_em != min_h_em) {\n        v->min_h_em = min_h_em;\n    }\n}\n\nstatic void ui_edit_insets(ui_edit_view_t* e) {\n    ui_view_t* v = &e->view;\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    e->inside = (ui_ltrb_t){\n        .left   = insets.left,\n        .top    = insets.top,\n        .right  = v->w - insets.right,\n        .bottom = v->h - insets.bottom\n    };\n    const int32_t width = e->edit.w; // previous width\n    e->edit.w = e->inside.right  - e->inside.left;\n    e->edit.h = e->inside.bottom - e->inside.top;\n    if (e->edit.w != width) { ui_edit_invalidate_all_runs(e); }\n}\n\nstatic void ui_edit_measure(ui_view_t* v) { // bottom up\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (v->w > 0 && e->sle) { ui_edit_prepare_sle(e); }\n    v->w = (int32_t)((fp64_t)v->fm->em.w * (fp64_t)v->min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)v->fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // enforce minimum size - it makes it checking corner cases much simpler\n    // and it's hard to edit anything in a smaller area - will result in bad UX\n    if (v->w < v->fm->em.w * 4) { v->w = i.left + v->fm->em.w * 4 + i.right; }\n    if (v->h < ui_edit_line_height(e))   { v->h = i.top + ui_edit_line_height(e) + i.bottom; }\n}\n\nstatic void ui_edit_layout(ui_view_t* v) { // top down\n    rt_assert(v->type == ui_view_text);\n    rt_assert(v->w > 0 && v->h > 0); // could be `if'\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_insets(e);\n    // fully visible runs\n    e->visible_runs = e->h / ui_edit_line_height(e);\n    ui_edit_invalidate_run(e, e->scroll.pn);\n    // number of runs in e->scroll.pn may have changed with e->w change\n    int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n    // glyph position in scroll_pn paragraph:\n    const ui_edit_pg_t scroll = v->w == 0 ? (ui_edit_pg_t){0, 0} :\n                                            ui_edit_scroll_pg(e);\n    e->scroll.rn = ui_edit_pg_to_pr(e, scroll).rn;\n    rt_assert(0 <= e->scroll.rn && e->scroll.rn < runs); (void)runs;\n    if (e->sle) { // single line edit (if changed on the fly):\n        e->selection.a[0].pn = 0; // only has single paragraph\n        e->selection.a[1].pn = 0;\n        // scroll line on top of current cursor position into view\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, 0, &runs);\n        if (runs <= 2 && e->scroll.rn == 1) {\n            ui_edit_pg_t top = scroll;\n            top.gp = rt_max(0, top.gp - run[e->scroll.rn].glyphs - 1);\n            ui_edit_scroll_into_view(e, top);\n        }\n    }\n    ui_edit_scroll_into_view(e, e->selection.a[1]);\n    ui_edit_caret_to(e, e->selection.a[1]);\n    if (e->focused) {\n        // recreate caret because fm->height may have changed\n        ui_edit_hide_caret(e);\n        ui_edit_destroy_caret(e);\n        ui_edit_create_caret(e);\n        ui_edit_show_caret(e);\n        rt_assert(e->focused);\n    }\n}\n\nstatic void ui_edit_paint_selection(ui_edit_view_t* e, int32_t y, const ui_edit_run_t* r,\n        const char* text, int32_t pn, int32_t c0, int32_t c1) {\n    uint64_t s0 = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t e0 = ui_edit_range.uint64(e->selection.a[1]);\n    if (s0 > e0) {\n        uint64_t swap = e0;\n        e0 = s0;\n        s0 = swap;\n    }\n    const ui_edit_pg_t pnc0 = {.pn = pn, .gp = c0};\n    const ui_edit_pg_t pnc1 = {.pn = pn, .gp = c1};\n    uint64_t s1 = ui_edit_range.uint64(pnc0);\n    uint64_t e1 = ui_edit_range.uint64(pnc1);\n    if (s0 <= e1 && s1 <= e0) {\n        uint64_t start = rt_max(s0, s1) - (uint64_t)c0;\n        uint64_t end = rt_min(e0, e1) - (uint64_t)c0;\n        if (start < end) {\n            int32_t fro = (int32_t)start;\n            int32_t to  = (int32_t)end;\n            int32_t ofs0 = ui_edit_str.gp_to_bp(text, r->bytes, fro);\n            int32_t ofs1 = ui_edit_str.gp_to_bp(text, r->bytes, to);\n            rt_swear(ofs0 >= 0 && ofs1 >= 0);\n            int32_t x0 = ui_edit_text_width(e, text, ofs0);\n            int32_t x1 = ui_edit_text_width(e, text, ofs1);\n            // selection color is MSVC dark mode selection color\n            // TODO: need light mode selection color tpp\n            ui_color_t sc = ui_color_rgb(0x26, 0x4F, 0x78); // selection color\n            if (!e->focused || !ui_app.focused()) {\n                sc = ui_colors.darken(sc, 0.1f);\n            }\n            const ui_ltrb_t insets = ui_view.margins(&e->view, &e->insets);\n            int32_t x = e->x + insets.left;\n            // event if background is transparent\n            ui_gdi.fill(x + x0, y, x1 - x0, ui_edit_line_height(e), sc);\n        }\n    }\n}\n\nstatic int32_t ui_edit_paint_paragraph(ui_edit_view_t* e,\n        const ui_gdi_ta_t* ta, int32_t x, int32_t y, int32_t pn,\n        ui_rect_t rc) {\n    static const char* ww = rt_glyph_south_west_arrow_with_hook;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pn, &runs);\n    for (int32_t j = ui_edit_first_visible_run(e, pn);\n                 j < runs && y < e->y + e->inside.bottom; j++) {\n//      rt_println(\"[%d.%d] @%d,%d bytes: %d\", pn, j, x, y, run[j].bytes);\n        if (rc.y - ui_edit_line_height(e) <= y && y < rc.y + rc.h) {\n            const char* text = str->u + run[j].bp;\n            ui_edit_paint_selection(e, y, &run[j], text, pn,\n                                    run[j].gp, run[j].gp + run[j].glyphs);\n            ui_gdi.text(ta, x, y, \"%.*s\", run[j].bytes, text);\n            if (j < runs - 1 && !e->hide_word_wrap) {\n                ui_gdi.text(ta, x + e->edit.w, y, \"%s\", ww);\n            }\n        }\n        y += ui_edit_line_height(e);\n    }\n    return y;\n}\n\nstatic void ui_edit_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    rt_assert(!ui_view.is_hidden(v));\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    // drawing text is really expensive, only paint what's needed:\n    ui_rect_t vrc = (ui_rect_t){v->x, v->y, v->w, v->h};\n    ui_rect_t rc;\n    if (ui.intersect_rect(&rc, &vrc, &ui_app.prc)) {\n        // because last line of the view may extend over the bottom\n        ui_gdi.set_clip(v->x, v->y, v->w, v->h);\n        ui_color_t b = v->background;\n        if (!ui_color_is_undefined(b) && !ui_color_is_transparent(b)) {\n            ui_gdi.fill(rc.x, rc.y, rc.w, rc.h, b);\n        }\n        const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n        int32_t x = v->x + insets.left;\n        int32_t y = v->y + insets.top;\n        const ui_gdi_ta_t ta = { .fm = v->fm, .color = v->color };\n        const int32_t pn = e->scroll.pn;\n        const int32_t bottom = v->y + e->inside.bottom;\n        rt_assert(pn < dt->np);\n        for (int32_t i = pn; i < dt->np && y < bottom; i++) {\n            y = ui_edit_paint_paragraph(e, &ta, x, y, i, rc);\n        }\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n}\n\nstatic void ui_edit_view_move(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    if (e->w > 0) {\n        ui_edit_move_caret(e, pg); // may select text on move\n    } else {\n        e->selection.a[1] = pg;\n    }\n    e->selection.a[0] = e->selection.a[1];\n}\n\nstatic bool ui_edit_reallocate_runs(ui_edit_view_t* e, int32_t p, int32_t np) {\n    // This function is called in after() callback when\n    // d->text.np already changed to `new_np`.\n    // It has to manipulate e->para[] array w/o calling\n    // ui_edit_invalidate_runs() ui_edit_dispose_all_runs()\n    // because they assume that e->para[] array is in sync\n    // d->text.np.\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    bool ok = true;\n    int32_t old_np = np;     // old (before) number of paragraphs\n    int32_t new_np = dt->np; // new (after)  number of paragraphs\n    rt_assert(old_np > 0 && new_np > 0 && e->para != null);\n    rt_assert(0 <= p && p < old_np);\n    if (old_np == new_np) {\n        ui_edit_invalidate_run(e, p);\n    } else if (new_np < old_np) { // shrinking - delete runs\n        const int32_t d = old_np - new_np; // `d` delta > 0\n        if (p + d < old_np - 1) {\n            const int32_t n = rt_max(0, old_np - p - d - 1);\n            memcpy(e->para + p + 1, e->para + p + 1 + d, n * sizeof(e->para[0]));\n        }\n        if (p < new_np) { ui_edit_invalidate_run(e, p); }\n        ok = rt_heap.realloc((void**)&e->para, new_np * sizeof(e->para[0])) == 0;\n        rt_swear(ok, \"shrinking\");\n    } else { // growing - insert runs\n        ui_edit_invalidate_run(e, p);\n        int32_t d = new_np - old_np;  // `d` delta > 0\n        ok = rt_heap.realloc_zero((void**)&e->para, new_np * sizeof(e->para[0])) == 0;\n        if (ok) {\n            const int32_t n = rt_max(0, new_np - p - d - 1);\n            memmove(e->para + p + 1 + d, e->para + p + 1,\n                    (size_t)n * sizeof(e->para[0]));\n            const int32_t m = rt_min(new_np, p + 1 + d);\n            for (int32_t i = p + 1; i < m; i++) {\n                e->para[i].run = null;\n                e->para[i].runs = 0;\n            }\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_before(ui_edit_notify_t* notify,\n         const ui_edit_notify_info_t* ni) {\n    ui_edit_notify_view_t* n = (ui_edit_notify_view_t*)notify;\n    ui_edit_view_t* e = (ui_edit_view_t*)n->that;\n    rt_swear(e->doc == ni->d);\n    if (e->w > 0 && e->h > 0) {\n        const ui_edit_text_t* dt = &e->doc->text; // document text\n        rt_assert(dt->np > 0);\n        // `n->data` is number of paragraphs before replace():\n        n->data = (uintptr_t)dt->np;\n        if (e->selection.from.pn != e->selection.to.pn) {\n            ui_edit_invalidate_view(e);\n        } else {\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        }\n    }\n}\n\nstatic void ui_edit_after(ui_edit_notify_t* notify,\n         const ui_edit_notify_info_t* ni) {\n    ui_edit_notify_view_t* n = (ui_edit_notify_view_t*)notify;\n    ui_edit_view_t* e = (ui_edit_view_t*)n->that;\n    const ui_edit_text_t* dt = &ni->d->text; // document text\n    rt_assert(ni->d == e->doc && dt->np > 0);\n    if (e->w > 0 && e->h > 0) {\n        // number of paragraphs before replace():\n        const int32_t np = (int32_t)n->data;\n        rt_swear(dt->np == np - ni->deleted + ni->inserted);\n        ui_edit_reallocate_runs(e, ni->r->from.pn, np);\n        e->selection = *ni->x;\n        // this is needed by undo/redo: trim selection\n        ui_edit_pg_t* pg = e->selection.a;\n        for (int32_t i = 0; i < rt_countof(e->selection.a); i++) {\n            pg[i].pn = rt_max(0, rt_min(dt->np - 1, pg[i].pn));\n            pg[i].gp = rt_max(0, rt_min(dt->ps[pg[i].pn].g, pg[i].gp));\n        }\n        if (ni->r->from.pn != ni->r->to.pn &&\n            ni->x->from.pn != ni->x->to.pn &&\n            ni->r->from.pn == ni->x->from.pn) {\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        } else {\n            ui_edit_invalidate_view(e);\n        }\n        ui_edit_scroll_into_view(e, e->selection.to);\n    }\n}\n\nstatic void ui_edit_view_init(ui_edit_view_t* e, ui_edit_doc_t* d) {\n    memset(e, 0, sizeof(*e));\n    rt_assert(d != null && d->text.np > 0);\n    e->doc = d;\n    rt_assert(d->text.np > 0);\n    e->listener.that = (void*)e;\n    e->listener.data = 0;\n    e->listener.notify.before = ui_edit_before;\n    e->listener.notify.after  = ui_edit_after;\n    rt_static_assertion(offsetof(ui_edit_notify_view_t, notify) == 0);\n    ui_edit_doc.subscribe(d, &e->listener.notify);\n    e->color_id = ui_color_id_window_text;\n    e->background_id = ui_color_id_window;\n    e->fm = &ui_app.fm.prop.normal;\n    e->insets  = (ui_margins_t){ 0.25, 0.25, 0.50, 0.25 };\n    e->padding = (ui_margins_t){ 0.25, 0.25, 0.25, 0.25 };\n    e->min_w_em = 1.0;\n    e->min_h_em = 1.0;\n    e->type = ui_view_text;\n    e->focusable = true;\n    e->last_x    = -1;\n    e->focused   = false;\n    e->sle       = false;\n    e->ro        = false;\n    e->caret        = (ui_point_t){-1, -1};\n    e->paint        = ui_edit_paint;\n    e->measure      = ui_edit_measure;\n    e->layout       = ui_edit_layout;\n    e->tap          = ui_edit_tap;\n    e->long_press   = ui_edit_long_press;\n    e->double_tap   = ui_edit_double_tap;\n    e->character    = ui_edit_character;\n    e->focus_gained = ui_edit_focus_gained;\n    e->focus_lost   = ui_edit_focus_lost;\n    e->key_pressed  = ui_edit_view_key_pressed;\n    e->mouse_scroll = ui_edit_mouse_scroll;\n    ui_edit_allocate_runs(e);\n    if (e->debug.id == null) { e->debug.id = \"#edit\"; }\n}\n\nstatic void ui_edit_view_dispose(ui_edit_view_t* e) {\n    ui_edit_doc.unsubscribe(e->doc, &e->listener.notify);\n    ui_edit_dispose_all_runs(e);\n    memset(e, 0, sizeof(*e));\n}\n\nui_edit_view_if ui_edit_view = {\n    .init                 = ui_edit_view_init,\n    .set_font             = ui_edit_view_set_font,\n    .move                 = ui_edit_view_move,\n    .replace              = ui_edit_view_replace,\n    .save                 = ui_edit_view_save,\n    .erase                = ui_edit_view_erase,\n    .cut                  = ui_edit_view_cut,\n    .copy                 = ui_edit_view_copy,\n    .paste                = ui_edit_view_paste,\n    .select_all           = ui_edit_select_all,\n    .key_down             = ui_edit_view_key_down,\n    .key_up               = ui_edit_view_key_up,\n    .key_left             = ui_edit_view_key_left,\n    .key_right            = ui_edit_view_key_right,\n    .key_page_up          = ui_edit_view_key_page_up,\n    .key_page_down        = ui_edit_view_key_page_down,\n    .key_home             = ui_edit_view_key_home,\n    .key_end              = ui_edit_view_key_end,\n    .key_delete           = ui_edit_view_key_delete,\n    .key_backspace        = ui_edit_view_key_backspace,\n    .key_enter            = ui_edit_view_key_enter,\n    .fuzz                 = null,\n    .dispose              = ui_edit_view_dispose\n};\n// _______________________________ ui_fuzzing.c _______________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n\n// TODO: Ctrl+A Ctrl+V Ctrl+C Ctrl+X Ctrl+Z Ctrl+Y\n\nstatic bool     ui_fuzzing_debug = true;\nstatic uint32_t ui_fuzzing_seed;\nstatic bool     ui_fuzzing_running;\nstatic bool     ui_fuzzing_inside;\n\nstatic ui_fuzzing_t ui_fuzzing_work;\n\nstatic const char* lorem_ipsum_words[] = {\n    \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\",\n    \"elit\", \"quisque\", \"faucibus\", \"ex\", \"sapien\", \"vitae\", \"pellentesque\",\n    \"sem\", \"placerat\", \"in\", \"id\", \"cursus\", \"mi\", \"pretium\", \"tellus\",\n    \"duis\", \"convallis\", \"tempus\", \"leo\", \"eu\", \"aenean\", \"sed\", \"diam\",\n    \"urna\", \"tempor\", \"pulvinar\", \"vivamus\", \"fringilla\", \"lacus\", \"nec\",\n    \"metus\", \"bibendum\", \"egestas\", \"iaculis\", \"massa\", \"nisl\",\n    \"malesuada\", \"lacinia\", \"integer\", \"nunc\", \"posuere\", \"ut\", \"hendrerit\",\n    \"semper\", \"vel\", \"class\", \"aptent\", \"taciti\", \"sociosqu\", \"ad\", \"litora\",\n    \"torquent\", \"per\", \"conubia\", \"nostra\", \"inceptos\",\n    \"himenaeos\", \"orci\", \"varius\", \"natoque\", \"penatibus\", \"et\", \"magnis\",\n    \"dis\", \"parturient\", \"montes\", \"nascetur\", \"ridiculus\", \"mus\", \"donec\",\n    \"rhoncus\", \"eros\", \"lobortis\", \"nulla\", \"molestie\", \"mattis\",\n    \"scelerisque\", \"maximus\", \"eget\", \"fermentum\", \"odio\", \"phasellus\",\n    \"non\", \"purus\", \"est\", \"efficitur\", \"laoreet\", \"mauris\", \"pharetra\",\n    \"vestibulum\", \"fusce\", \"dictum\", \"risus\", \"blandit\", \"quis\",\n    \"suspendisse\", \"aliquet\", \"nisi\", \"sodales\", \"consequat\", \"magna\",\n    \"ante\", \"condimentum\", \"neque\", \"at\", \"luctus\", \"nibh\", \"finibus\",\n    \"facilisis\", \"dapibus\", \"etiam\", \"interdum\", \"tortor\", \"ligula\",\n    \"congue\", \"sollicitudin\", \"erat\", \"viverra\", \"ac\", \"tincidunt\", \"nam\",\n    \"porta\", \"elementum\", \"a\", \"enim\", \"euismod\", \"quam\", \"justo\",\n    \"lectus\", \"commodo\", \"augue\", \"arcu\", \"dignissim\", \"velit\", \"aliquam\",\n    \"imperdiet\", \"mollis\", \"nullam\", \"volutpat\", \"porttitor\",\n    \"ullamcorper\", \"rutrum\", \"gravida\", \"cras\", \"eleifend\", \"turpis\",\n    \"fames\", \"primis\", \"vulputate\", \"ornare\", \"sagittis\", \"vehicula\",\n    \"praesent\", \"dui\", \"felis\", \"venenatis\", \"ultrices\", \"proin\", \"libero\",\n    \"feugiat\", \"tristique\", \"accumsan\", \"maecenas\", \"potenti\", \"ultricies\",\n    \"habitant\", \"morbi\", \"senectus\", \"netus\", \"suscipit\", \"auctor\",\n    \"curabitur\", \"facilisi\", \"cubilia\", \"curae\", \"hac\", \"habitasse\",\n    \"platea\", \"dictumst\"\n};\n\n#define ui_fuzzing_lorem_ipsum_canonique \\\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do \"         \\\n    \"eiusmod  tempor incididunt ut labore et dolore magna aliqua.Ut enim ad \"  \\\n    \"minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \" \\\n    \"ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \"      \\\n    \"voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur \"  \\\n    \"sint occaecat cupidatat non proident, sunt in culpa qui officia \"         \\\n    \"deserunt mollit anim id est laborum.\"\n\n#define ui_fuzzing_lorem_ipsum_chinese \\\n    \"\\xE6\\x88\\x91\\xE6\\x98\\xAF\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE6\\x96\\x87\\xE6\\x9C\\xAC\\xE7\\x9A\\x84\\xE4\" \\\n    \"\\xBD\\x8D\\xE7\\xBD\\xAE\\xE3\\x80\\x82\\xE8\\xBF\\x99\\xE9\\x87\\x8C\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE4\\xBA\" \\\n    \"\\x86\\xE5\\x81\\x87\\xE6\\x96\\x87\\xE5\\x81\\x87\\xE5\\xAD\\x97\\xE3\\x80\\x82\\xE5\\xB8\\x8C\\xE6\\x9C\\x9B\" \\\n    \"\\xE8\\xBF\\x99\\xE4\\xBA\\x9B\\xE6\\x96\\x87\\xE5\\xAD\\x97\\xE5\\x8F\\xAF\\xE4\\xBB\\xA5\\xE5\\xA1\\xAB\\xE5\" \\\n    \"\\x85\\x85\\xE7\\xA9\\xBA\\xE7\\x99\\xBD\\xE3\\x80\\x82\";\n\n#define ui_fuzzing_lorem_ipsum_japanese \\\n    \"\\xE3\\x81\\x93\\xE3\\x82\\x8C\\xE3\\x81\\xAF\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\" \\\n    \"\\x82\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x81\\xA7\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE3\\x81\\x93\\xE3\\x81\" \\\n    \"\\x93\\xE3\\x81\\xAB\\xE6\\x96\\x87\\xE7\\xAB\\xA0\\xE3\\x81\\x8C\\xE5\\x85\\xA5\\xE3\\x82\\x8A\\xE3\\x81\\xBE\" \\\n    \"\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE8\\xAA\\xAD\\xE3\\x81\\xBF\\xE3\\x82\\x84\\xE3\\x81\\x99\\xE3\\x81\\x84\\xE3\" \\\n    \"\\x82\\x88\\xE3\\x81\\x86\\xE3\\x81\\xAB\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\\x82\" \\\n    \"\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x82\\x92\\xE4\\xBD\\xBF\\xE7\\x94\\xA8\\xE3\\x81\\x97\\xE3\\x81\\xA6\" \\\n    \"\\xE3\\x81\\x84\\xE3\\x81\\xBE\\xE3\\x81\\x99\\xE3\\x80\\x82\";\n\n\n#define ui_fuzzing_lorem_ipsum_korean \\\n    \"\\xEC\\x9D\\xB4\\xEA\\xB2\\x83\\xEC\\x9D\\x80\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\\x85\\x8D\\xEC\\x8A\" \\\n    \"\\xA4\\xED\\x8A\\xB8\\xEC\\x9E\\x85\\xEB\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\\x20\\xEC\\x97\\xAC\\xEA\\xB8\\xB0\\xEC\" \\\n    \"\\x97\\x90\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEB\\x93\\x9C\\xEC\\x96\\xB4\\xEA\\xB0\\x80\" \\\n    \"\\xEB\\x8A\\x94\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEC\\x9E\\x88\\xEB\\x8B\\xA4\\x2E\\x20\" \\\n    \"\\xEC\\x9D\\xBD\\xEA\\xB8\\xB0\\x20\\xEC\\x89\\xBD\\xEA\\xB2\\x8C\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\" \\\n    \"\\x85\\x8D\\xEC\\x8A\\xA4\\xED\\x8A\\xB8\\xEB\\xA5\\xBC\\x20\\xEC\\x82\\xAC\\xEC\\x9A\\xA9\\xED\\x95\\xA9\\xEB\" \\\n    \"\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\";\n\n#define ui_fuzzing_lorem_ipsum_emoji \\\n    \"\\xF0\\x9F\\x8D\\x95\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA6\\x84\\xF0\\x9F\\x92\\xBB\\xF0\\x9F\\x8E\\x89\\xF0\\x9F\" \\\n    \"\\x8C\\x88\\xF0\\x9F\\x90\\xB1\\xF0\\x9F\\x93\\x9A\\xF0\\x9F\\x8E\\xA8\\xF0\\x9F\\x8D\\x94\\xF0\\x9F\\x8D\\xA6\" \\\n    \"\\xF0\\x9F\\x8E\\xB8\\xF0\\x9F\\xA7\\xA9\\xF0\\x9F\\x8D\\xBF\\xF0\\x9F\\x93\\xB7\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\" \\\n    \"\\x91\\xBE\\xF0\\x9F\\x8C\\xAE\\xF0\\x9F\\x8E\\x88\\xF0\\x9F\\x9A\\xB2\\xF0\\x9F\\x8D\\xA9\\xF0\\x9F\\x8E\\xAE\" \\\n    \"\\xF0\\x9F\\x8D\\x89\\xF0\\x9F\\x8E\\xAC\\xF0\\x9F\\x90\\xB6\\xF0\\x9F\\x93\\xB1\\xF0\\x9F\\x8E\\xB9\\xF0\\x9F\" \\\n    \"\\xA6\\x96\\xF0\\x9F\\x8C\\x9F\\xF0\\x9F\\x8D\\xAD\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\\x8F\\x96\\xF0\\x9F\\xA6\\x8B\" \\\n    \"\\xF0\\x9F\\x8E\\xB2\\xF0\\x9F\\x8E\\xAF\\xF0\\x9F\\x8D\\xA3\\xF0\\x9F\\x9A\\x81\\xF0\\x9F\\x8E\\xAD\\xF0\\x9F\" \\\n    \"\\x91\\x9F\\xF0\\x9F\\x9A\\x82\\xF0\\x9F\\x8D\\xAA\\xF0\\x9F\\x8E\\xBB\\xF0\\x9F\\x9B\\xB8\\xF0\\x9F\\x8C\\xBD\" \\\n    \"\\xF0\\x9F\\x93\\x80\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA7\\x81\\xF0\\x9F\\x93\\xAF\\xF0\\x9F\\x8C\\xAF\\xF0\\x9F\" \\\n    \"\\x90\\xA5\\xF0\\x9F\\xA7\\x83\\xF0\\x9F\\x8D\\xBB\\xF0\\x9F\\x8E\\xAE\";\n\ntypedef struct {\n    char* text;\n    int32_t count; // at least 1KB\n    uint32_t seed; // seed for random generator\n    int32_t min_paragraphs; // at least 1\n    int32_t max_paragraphs;\n    int32_t min_sentences; // at least 1\n    int32_t max_sentences;\n    int32_t min_words; // at least 2\n    int32_t max_words;\n    const char* append; // append after each paragraph (e.g. extra \"\\n\")\n} ui_fuzzing_generator_params_t;\n\nstatic uint32_t ui_fuzzing_random(void) {\n    return rt_num.random32(&ui_fuzzing_seed);\n}\n\nstatic fp64_t ui_fuzzing_random_fp64(void) {\n    uint32_t r = ui_fuzzing_random();\n    return (fp64_t)r / (fp64_t)UINT32_MAX;\n}\n\nstatic void ui_fuzzing_generator(ui_fuzzing_generator_params_t p) {\n    rt_fatal_if(p.count < 1024); // at least 1KB expected\n    rt_fatal_if_not(0 < p.min_paragraphs && p.min_paragraphs <= p.max_paragraphs);\n    rt_fatal_if_not(0 < p.min_sentences && p.min_sentences <= p.max_sentences);\n    rt_fatal_if_not(2 < p.min_words && p.min_words <= p.max_words);\n    char* s = p.text;\n    // assume longest word is less than 128\n    char* end = p.text + p.count - 128;\n    uint32_t paragraphs = p.min_paragraphs +\n        (p.min_paragraphs == p.max_paragraphs ? 0 :\n         rt_num.random32(&p.seed) % (p.max_paragraphs - p.min_paragraphs + 1));\n    while (paragraphs > 0 && s < end) {\n        uint32_t sentences_in_paragraph = p.min_sentences +\n            (p.min_sentences == p.max_sentences ? 0 :\n             rt_num.random32(&p.seed) % (p.max_sentences - p.min_sentences + 1));\n        while (sentences_in_paragraph > 0 && s < end) {\n            const uint32_t words_in_sentence = p.min_words +\n                (p.min_words == p.max_words ? 0 :\n                 rt_num.random32(&p.seed) % (p.max_words - p.min_words + 1));\n            for (uint32_t i = 0; i < words_in_sentence && s < end; i++) {\n                const int32_t ix = rt_num.random32(&p.seed) %\n                                   rt_countof(lorem_ipsum_words);\n                const char* word = lorem_ipsum_words[ix];\n                memcpy(s, word, strlen(word));\n                if (i == 0) { *s = (char)toupper(*s); }\n                s += strlen(word);\n                if (i < words_in_sentence - 1 && s < end) {\n                    const char* delimiter = \"\\x20\";\n                    int32_t punctuation = rt_num.random32(&p.seed) % 128;\n                    switch (punctuation) {\n                        case 0:\n                        case 1:\n                        case 2: delimiter = \", \"; break;\n                        case 3:\n                        case 4: delimiter = \"; \"; break;\n                        case 6: delimiter = \": \"; break;\n                        case 7: delimiter = \" - \"; break;\n                        default: break;\n                    }\n                    memcpy(s, delimiter, strlen(delimiter));\n                    s += strlen(delimiter);\n                }\n            }\n            if (sentences_in_paragraph > 1 && s < end) {\n                memcpy(s, \".\\x20\", 2);\n                s += 2;\n            } else {\n                *s++ = '.';\n            }\n            sentences_in_paragraph--;\n        }\n        if (paragraphs > 1 && s < end) {\n            *s++ = '\\n';\n        }\n        if (p.append != null && p.append[0] != 0) {\n            memcpy(s, p.append, strlen(p.append));\n            s += strlen(p.append);\n        }\n        paragraphs--;\n    }\n    *s = 0;\n//  rt_println(\"%s\\n\", p.text);\n}\n\nstatic void ui_fuzzing_next_gibberish(int32_t number_of_characters,\n        char text[]) {\n    static fp64_t freq[96] = {\n        0.1716, 0.0023, 0.0027, 0.0002, 0.0001, 0.0005, 0.0013, 0.0012,\n        0.0015, 0.0014, 0.0017, 0.0002, 0.0084, 0.0020, 0.0075, 0.0040,\n        0.0135, 0.0045, 0.0053, 0.0053, 0.0047, 0.0047, 0.0043, 0.0047,\n        0.0057, 0.0044, 0.0037, 0.0004, 0.0016, 0.0004, 0.0017, 0.0017,\n        0.0020, 0.0045, 0.0026, 0.0020, 0.0027, 0.0021, 0.0025, 0.0026,\n        0.0030, 0.0025, 0.0021, 0.0018, 0.0028, 0.0026, 0.0024, 0.0020,\n        0.0025, 0.0026, 0.0030, 0.0022, 0.0027, 0.0022, 0.0020, 0.0023,\n        0.0015, 0.0016, 0.0009, 0.0005, 0.0005, 0.0001, 0.0003, 0.0003,\n        0.0078, 0.0013, 0.0012, 0.0008, 0.0012, 0.0007, 0.0006, 0.0011,\n        0.0016, 0.0012, 0.0011, 0.0004, 0.0004, 0.0016, 0.0013, 0.0009,\n        0.0009, 0.0008, 0.0013, 0.0011, 0.0013, 0.0012, 0.0006, 0.0007,\n        0.0011, 0.0005, 0.0007, 0.0003, 0.0002, 0.0006, 0.0002, 0.0005\n    };\n    static fp64_t cumulative_freq[96];\n    static bool initialized = 0;\n    if (!initialized) {\n        cumulative_freq[0] = freq[0];\n        for (int i = 1; i < rt_countof(freq); i++) {\n            cumulative_freq[i] = cumulative_freq[i - 1] + freq[i];\n        }\n        initialized = 1;\n    }\n    int32_t i = 0;\n    while (i < number_of_characters) {\n        text[i] = 0x00;\n        fp64_t r = ui_fuzzing_random_fp64();\n        for (int j = 0; j < 96 && text[i] == 0; j++) {\n            if (r < cumulative_freq[j]) {\n                text[i] = (char)(0x20 + j);\n            }\n        }\n        if (text[i] != 0) { i++; }\n    }\n    text[number_of_characters] = 0x00;\n}\n\nstatic void ui_fuzzing_dispatch(ui_fuzzing_t* work) {\n    rt_swear(work == &ui_fuzzing_work);\n    ui_app.alt = work->alt;\n    ui_app.ctrl = work->ctrl;\n    ui_app.shift = work->shift;\n    if (work->utf8 != null && work->utf8[0] != 0) {\n        ui_view.character(ui_app.content, work->utf8);\n        work->utf8 = work->utf8[1] == 0 ? null : work->utf8++;\n    } else if (work->key != 0) {\n        ui_view.key_pressed(ui_app.content, work->key);\n        ui_view.key_released(ui_app.content, work->key);\n        work->key = 0;\n    } else if (work->pt != null) {\n        const int32_t x = work->pt->x;\n        const int32_t y = work->pt->y;\n        ui_app.mouse.x = x;\n        ui_app.mouse.y = y;\n//      https://stackoverflow.com/questions/22259936/\n//      https://stackoverflow.com/questions/65691101/\n//      rt_println(\"%d,%d\", x + ui_app.wrc.x, y + ui_app.wrc.y);\n//      // next line works only when running as administrator:\n//      rt_fatal_win32err(SetCursorPos(x + ui_app.wrc.x, y + ui_app.wrc.y));\n        const bool l_button = ui_app.mouse_left  != work->left;\n        const bool r_button = ui_app.mouse_right != work->right;\n        ui_app.mouse_left  = work->left;\n        ui_app.mouse_right = work->right;\n        ui_view.mouse_move(ui_app.content);\n        if (l_button) {\n            ui_view.tap(ui_app.content, 0, work->left);\n        }\n        if (r_button) {\n            ui_view.tap(ui_app.content, 2, work->right);\n        }\n        work->pt = null;\n    } else {\n        rt_assert(false, \"TODO: ?\");\n    }\n    if (ui_fuzzing_running) {\n        if (ui_fuzzing.next == null) {\n            ui_fuzzing.next_random(work);\n        } else {\n            ui_fuzzing.next(work);\n        }\n    }\n}\n\nstatic void ui_fuzzing_do_work(rt_work_t* p) {\n    if (ui_fuzzing_running) {\n        ui_fuzzing_inside = true;\n        if (ui_fuzzing.custom != null) {\n            ui_fuzzing.custom((ui_fuzzing_t*)p);\n        } else {\n            ui_fuzzing.dispatch((ui_fuzzing_t*)p);\n        }\n        ui_fuzzing_inside = false;\n    } else {\n        // fuzzing has been .stop()-ed drop it\n    }\n}\n\nstatic void ui_fuzzing_post(void) {\n    ui_app.post(&ui_fuzzing_work.base);\n}\n\nstatic void ui_fuzzing_alt_ctrl_shift(void) {\n    ui_fuzzing_t* w = &ui_fuzzing_work;\n    switch (ui_fuzzing_random() % 8) {\n        case 0: w->alt = 0; w->ctrl = 0; w->shift = 0; break;\n        case 1: w->alt = 1; w->ctrl = 0; w->shift = 0; break;\n        case 2: w->alt = 0; w->ctrl = 1; w->shift = 0; break;\n        case 3: w->alt = 1; w->ctrl = 1; w->shift = 0; break;\n        case 4: w->alt = 0; w->ctrl = 0; w->shift = 1; break;\n        case 5: w->alt = 1; w->ctrl = 0; w->shift = 1; break;\n        case 6: w->alt = 0; w->ctrl = 1; w->shift = 1; break;\n        case 7: w->alt = 1; w->ctrl = 1; w->shift = 1; break;\n        default: rt_assert(false);\n    }\n}\n\nstatic void ui_fuzzing_character(void) {\n    static char utf8[4 * 1024];\n    if (ui_fuzzing_work.utf8 == null) {\n        fp64_t r = ui_fuzzing_random_fp64();\n        if (r < 0.125) {\n            uint32_t rnd = ui_fuzzing_random();\n            int32_t n = (int32_t)rt_max(1, rnd % 32);\n            ui_fuzzing_next_gibberish(n, utf8);\n            ui_fuzzing_work.utf8 = utf8;\n            if (ui_fuzzing_debug) {\n    //          rt_println(\"%s\", utf8);\n            }\n        } else if (r < 0.25) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_chinese;\n        } else if (r < 0.375) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_japanese;\n        } else if (r < 0.5) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_korean;\n        } else if (r < 0.5 + 0.125) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_emoji;\n        } else {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_canonique;\n        }\n    }\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_key(void) {\n    struct {\n        int32_t key;\n        const char* name;\n    } keys[] = {\n        { ui.key.up,        \"up\",     },\n        { ui.key.down,      \"down\",   },\n        { ui.key.left,      \"left\",   },\n        { ui.key.right,     \"right\",  },\n        { ui.key.home,      \"home\",   },\n        { ui.key.end,       \"end\",    },\n        { ui.key.page_up,   \"pgup\",   },\n        { ui.key.page_down, \"pgdw\",   },\n        { ui.key.insert,    \"insert\"  },\n        { ui.key.enter,     \"enter\"   },\n        { ui.key.del,       \"delete\"  },\n        { ui.key.back,      \"back\"    },\n    };\n    ui_fuzzing_alt_ctrl_shift();\n    uint32_t ix = ui_fuzzing_random() % rt_countof(keys);\n    if (ui_fuzzing_debug) {\n//      rt_println(\"key(%s)\", keys[ix].name);\n    }\n    ui_fuzzing_work.key = keys[ix].key;\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_mouse(void) {\n    // mouse events only inside edit control otherwise\n    // they will start clicking buttons around\n    ui_view_t* v = ui_app.content;\n    ui_fuzzing_t* w = &ui_fuzzing_work;\n    int32_t x = ui_fuzzing_random() % v->w;\n    int32_t y = ui_fuzzing_random() % v->h;\n    static ui_point_t pt;\n    pt = (ui_point_t){ x + v->x, y + v->y };\n    if (ui_fuzzing_random() % 2) {\n        w->left  = !w->left;\n    }\n    if (ui_fuzzing_random() % 2) {\n        w->right = !w->right;\n    }\n    if (ui_fuzzing_debug) {\n//      rt_println(\"mouse(%d,%d) %s%s\", pt.x, pt.y,\n//              w->left ? \"L\" : \"_\", w->right ? \"R\" : \"_\");\n    }\n    w->pt = &pt;\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_start(uint32_t seed) {\n    ui_fuzzing_seed = seed | 0x1;\n    ui_fuzzing_running = true;\n    if (ui_fuzzing.next == null) {\n        ui_fuzzing.next_random(&ui_fuzzing_work);\n    } else {\n        ui_fuzzing.next(&ui_fuzzing_work);\n    }\n}\n\nstatic bool ui_fuzzing_is_running(void) {\n    return ui_fuzzing_running;\n}\n\nstatic bool ui_fuzzing_from_inside(void) {\n    return ui_fuzzing_inside;\n}\n\nstatic void ui_fuzzing_stop(void) {\n    ui_fuzzing_running = false;\n}\n\nstatic void ui_fuzzing_next_random(ui_fuzzing_t* f) {\n    rt_swear(f == &ui_fuzzing_work);\n    ui_fuzzing_work = (ui_fuzzing_t){\n        .base = { .when = rt_clock.seconds() + 0.001, // 1ms\n                  .work = ui_fuzzing_do_work },\n    };\n    uint32_t rnd = ui_fuzzing_random() % 100;\n    if (rnd < 80) {\n        ui_fuzzing_character();\n    } else if (rnd < 90) {\n        ui_fuzzing_key();\n    } else {\n        ui_fuzzing_mouse();\n    }\n}\n\nui_fuzzing_if ui_fuzzing = {\n    .start       = ui_fuzzing_start,\n    .is_running  = ui_fuzzing_is_running,\n    .from_inside = ui_fuzzing_from_inside,\n    .next_random = ui_fuzzing_next_random,\n    .dispatch    = ui_fuzzing_dispatch,\n    .next        = null,\n    .custom      = null,\n    .stop        = ui_fuzzing_stop\n};\n// _________________________________ ui_gdi.c _________________________________\n\n#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n#pragma push_macro(\"ui_gdi_with_hdc\")\n#pragma push_macro(\"ui_gdi_hdc_with_font\")\n\nstatic ui_brush_t  ui_gdi_brush_hollow;\nstatic ui_brush_t  ui_gdi_brush_color;\nstatic ui_pen_t    ui_gdi_pen_hollow;\nstatic ui_region_t ui_gdi_clip;\n\ntypedef struct ui_gdi_context_s {\n    HDC hdc; // window canvas() or memory DC\n    int32_t background_mode;\n    int32_t stretch_mode;\n    ui_pen_t pen;\n    ui_font_t font;\n    ui_color_t text_color;\n    POINT brush_origin;\n    ui_brush_t brush;\n    HBITMAP texture;\n} ui_gdi_context_t;\n\nstatic ui_gdi_context_t ui_gdi_context;\n\n#define ui_gdi_hdc() (ui_gdi_context.hdc)\n\nstatic void ui_gdi_init(void) {\n    ui_gdi_brush_hollow = (ui_brush_t)GetStockBrush(HOLLOW_BRUSH);\n    ui_gdi_brush_color  = (ui_brush_t)GetStockBrush(DC_BRUSH);\n    ui_gdi_pen_hollow = (ui_pen_t)GetStockPen(NULL_PEN);\n}\n\nstatic void ui_gdi_fini(void) {\n    if (ui_gdi_clip != null) {\n        rt_fatal_win32err(DeleteRgn(ui_gdi_clip));\n    }\n    ui_gdi_clip = null;\n}\n\nstatic ui_pen_t ui_gdi_set_pen(ui_pen_t p) {\n    rt_not_null(p);\n    return (ui_pen_t)SelectPen(ui_gdi_hdc(), (HPEN)p);\n}\n\nstatic ui_brush_t ui_gdi_set_brush(ui_brush_t b) {\n    rt_not_null(b);\n    return (ui_brush_t)SelectBrush(ui_gdi_hdc(), b);\n}\n\nstatic uint32_t ui_gdi_color_rgb(ui_color_t c) {\n    rt_assert(ui_color_is_8bit(c));\n    return (COLORREF)(c & 0xFFFFFFFF);\n}\n\nstatic COLORREF ui_gdi_color_ref(ui_color_t c) {\n    return ui_gdi.color_rgb(c);\n}\n\nstatic ui_color_t ui_gdi_set_text_color(ui_color_t c) {\n    return SetTextColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n}\n\nstatic ui_font_t ui_gdi_set_font(ui_font_t f) {\n    rt_not_null(f);\n    return (ui_font_t)SelectFont(ui_gdi_hdc(), (HFONT)f);\n}\n\nstatic void ui_gdi_begin(ui_bitmap_t* image) {\n    rt_swear(ui_gdi_context.hdc == null, \"no nested begin()/end()\");\n    if (image != null) {\n        rt_swear(image->texture != null);\n        ui_gdi_context.hdc = CreateCompatibleDC((HDC)ui_app.canvas);\n        ui_gdi_context.texture = SelectBitmap(ui_gdi_hdc(),\n                                             (HBITMAP)image->texture);\n    } else {\n        ui_gdi_context.hdc = (HDC)ui_app.canvas;\n        rt_swear(ui_gdi_context.texture == null);\n    }\n    ui_gdi_context.font  = ui_gdi_set_font(ui_app.fm.prop.normal.font);\n    ui_gdi_context.pen   = ui_gdi_set_pen(ui_gdi_pen_hollow);\n    ui_gdi_context.brush = ui_gdi_set_brush(ui_gdi_brush_hollow);\n    rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0,\n        &ui_gdi_context.brush_origin));\n    ui_color_t tc = ui_colors.get_color(ui_color_id_window_text);\n    ui_gdi_context.text_color = ui_gdi_set_text_color(tc);\n    ui_gdi_context.background_mode = SetBkMode(ui_gdi_hdc(), TRANSPARENT);\n    ui_gdi_context.stretch_mode = SetStretchBltMode(ui_gdi_hdc(), HALFTONE);\n}\n\nstatic void ui_gdi_end(void) {\n    rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(),\n                   ui_gdi_context.brush_origin.x,\n                   ui_gdi_context.brush_origin.y, null));\n    ui_gdi_set_brush(ui_gdi_context.brush);\n    ui_gdi_set_pen(ui_gdi_context.pen);\n    ui_gdi_set_text_color(ui_gdi_context.text_color);\n    SetBkMode(ui_gdi_hdc(), ui_gdi_context.background_mode);\n    SetStretchBltMode(ui_gdi_hdc(), ui_gdi_context.stretch_mode);\n    if (ui_gdi_context.hdc != (HDC)ui_app.canvas) {\n        rt_swear(ui_gdi_context.texture != null); // 1x1 bitmap\n        SelectBitmap(ui_gdi_context.hdc, (HBITMAP)ui_gdi_context.texture);\n        rt_fatal_win32err(DeleteDC(ui_gdi_context.hdc));\n    }\n    memset(&ui_gdi_context, 0x00, sizeof(ui_gdi_context));\n}\n\nstatic ui_pen_t ui_gdi_set_colored_pen(ui_color_t c) {\n    ui_pen_t p = (ui_pen_t)SelectPen(ui_gdi_hdc(), GetStockPen(DC_PEN));\n    SetDCPenColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n    return p;\n}\n\nstatic ui_pen_t ui_gdi_create_pen(ui_color_t c, int32_t width) {\n    rt_assert(width >= 1);\n    ui_pen_t pen = (ui_pen_t)CreatePen(PS_SOLID, width, ui_gdi_color_ref(c));\n    rt_not_null(pen);\n    return pen;\n}\n\nstatic void ui_gdi_delete_pen(ui_pen_t p) {\n    rt_fatal_win32err(DeletePen(p));\n}\n\nstatic ui_brush_t ui_gdi_create_brush(ui_color_t c) {\n    return (ui_brush_t)CreateSolidBrush(ui_gdi_color_ref(c));\n}\n\nstatic void ui_gdi_delete_brush(ui_brush_t b) {\n    DeleteBrush((HBRUSH)b);\n}\n\nstatic ui_color_t ui_gdi_set_brush_color(ui_color_t c) {\n    return SetDCBrushColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n}\n\nstatic void ui_gdi_set_clip(int32_t x, int32_t y, int32_t w, int32_t h) {\n    if (ui_gdi_clip != null) { DeleteRgn(ui_gdi_clip); ui_gdi_clip = null; }\n    if (w > 0 && h > 0) {\n        ui_gdi_clip = (ui_region_t)CreateRectRgn(x, y, x + w, y + h);\n        rt_not_null(ui_gdi_clip);\n    }\n    rt_fatal_if(SelectClipRgn(ui_gdi_hdc(), (HRGN)ui_gdi_clip) == ERROR);\n}\n\nstatic void ui_gdi_pixel(int32_t x, int32_t y, ui_color_t c) {\n    rt_not_null(ui_app.canvas);\n    rt_fatal_win32err(SetPixel(ui_gdi_hdc(), x, y, ui_gdi_color_ref(c)));\n}\n\nstatic void ui_gdi_rectangle(int32_t x, int32_t y, int32_t w, int32_t h) {\n    rt_fatal_win32err(Rectangle(ui_gdi_hdc(), x, y, x + w, y + h));\n}\n\nstatic void ui_gdi_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1,\n        ui_color_t c) {\n    POINT pt;\n    rt_fatal_win32err(MoveToEx(ui_gdi_hdc(), x0, y0, &pt));\n    ui_pen_t p = ui_gdi_set_colored_pen(c);\n    rt_fatal_win32err(LineTo(ui_gdi_hdc(), x1, y1));\n    ui_gdi_set_pen(p);\n    rt_fatal_win32err(MoveToEx(ui_gdi_hdc(), pt.x, pt.y, null));\n}\n\nstatic void ui_gdi_frame(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t c) {\n    ui_brush_t b = ui_gdi_set_brush(ui_gdi_brush_hollow);\n    ui_pen_t p = ui_gdi_set_colored_pen(c);\n    ui_gdi_rectangle(x, y, w, h);\n    ui_gdi_set_pen(p);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_rect(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t border, ui_color_t fill) {\n    const bool tf = ui_color_is_transparent(fill);   // transparent fill\n    const bool tb = ui_color_is_transparent(border); // transparent border\n    ui_brush_t b = tf ? ui_gdi_brush_hollow : ui_gdi_brush_color;\n    b = ui_gdi_set_brush(b);\n    ui_color_t c = tf ? ui_colors.transparent : ui_gdi_set_brush_color(fill);\n    ui_pen_t p = tb ? ui_gdi_set_pen(ui_gdi_pen_hollow) :\n                      ui_gdi_set_colored_pen(border);\n    ui_gdi_rectangle(x, y, w, h);\n    if (!tf) { ui_gdi_set_brush_color(c); }\n    ui_gdi_set_pen(p);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_fill(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t c) {\n//  rt_println(\"%d,%d %dx%d 0x%08X\", x, y, w, h, (uint32_t)c);\n    ui_brush_t b = ui_gdi_set_brush(ui_gdi_brush_color);\n    c = ui_gdi_set_brush_color(c);\n    RECT rc = { x, y, x + w, y + h };\n    HBRUSH brush = (HBRUSH)GetCurrentObject(ui_gdi_hdc(), OBJ_BRUSH);\n    rt_fatal_win32err(FillRect(ui_gdi_hdc(), &rc, brush));\n    ui_gdi_set_brush_color(c);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_poly(ui_point_t* points, int32_t count, ui_color_t c) {\n    // make sure ui_point_t and POINT have the same memory layout:\n    static_assert(sizeof(points->x) == sizeof(((POINT*)0)->x), \"ui_point_t\");\n    static_assert(sizeof(points->y) == sizeof(((POINT*)0)->y), \"ui_point_t\");\n    static_assert(sizeof(points[0]) == sizeof(*((POINT*)0)), \"ui_point_t\");\n    rt_assert(ui_gdi_hdc() != null && count > 1);\n    ui_pen_t pen = ui_gdi_set_colored_pen(c);\n    rt_fatal_win32err(Polyline(ui_gdi_hdc(), (POINT*)points, count));\n    ui_gdi_set_pen(pen);\n}\n\nstatic void ui_gdi_circle(int32_t x, int32_t y, int32_t radius,\n        ui_color_t border, ui_color_t fill) {\n    rt_swear(!ui_color_is_transparent(border) || ui_color_is_transparent(fill));\n    // Win32 GDI even radius drawing looks ugly squarish and asymmetrical.\n    rt_swear(radius % 2 == 1, \"radius: %d must be odd\");\n    if (ui_color_is_transparent(border)) {\n        rt_assert(!ui_color_is_transparent(fill));\n        border = fill;\n    }\n    rt_assert(!ui_color_is_transparent(border));\n    const bool tf = ui_color_is_transparent(fill);   // transparent fill\n    ui_brush_t brush = tf ? ui_gdi_set_brush(ui_gdi_brush_hollow) :\n                        ui_gdi_set_brush(ui_gdi_brush_color);\n    ui_color_t c = tf ? ui_colors.transparent : ui_gdi_set_brush_color(fill);\n    ui_pen_t p = ui_gdi_set_colored_pen(border);\n    HDC hdc = ui_gdi_context.hdc;\n    int32_t l = x - radius;\n    int32_t t = y - radius;\n    int32_t r = x + radius + 1;\n    int32_t b = y + radius + 1;\n    Ellipse(hdc, l, t, r, b);\n//  SetPixel(hdc, x, y, RGB(255, 255, 255));\n    ui_gdi_set_pen(p);\n    if (!tf) { ui_gdi_set_brush_color(c); }\n    ui_gdi_set_brush(brush);\n}\n\nstatic void ui_gdi_fill_rounded(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t fill) {\n    int32_t r = x + w - 1; // right\n    int32_t b = y + h - 1; // bottom\n    ui_gdi_circle(x + radius, y + radius, radius, fill, fill);\n    ui_gdi_circle(r - radius, y + radius, radius, fill, fill);\n    ui_gdi_circle(x + radius, b - radius, radius, fill, fill);\n    ui_gdi_circle(r - radius, b - radius, radius, fill, fill);\n    // rectangles\n    ui_gdi.fill(x + radius, y, w - radius * 2, h, fill);\n    r = x + w - radius;\n    ui_gdi.fill(x, y + radius, radius, h - radius * 2, fill);\n    ui_gdi.fill(r, y + radius, radius, h - radius * 2, fill);\n}\n\nstatic void ui_gdi_rounded_border(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t border) {\n    {\n        int32_t r = x + w - 1; // right\n        int32_t b = y + h - 1; // bottom\n        ui_gdi.set_clip(x, y, radius + 1, radius + 1);\n        ui_gdi_circle(x + radius, y + radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(r - radius, y, radius + 1, radius + 1);\n        ui_gdi_circle(r - radius, y + radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(x, b - radius, radius + 1, radius + 1);\n        ui_gdi_circle(x + radius, b - radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(r - radius, b - radius, radius + 1, radius + 1);\n        ui_gdi_circle(r - radius, b - radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n    {\n        int32_t r = x + w - 1; // right\n        int32_t b = y + h - 1; // bottom\n        ui_gdi.line(x + radius, y, r - radius + 1, y, border);\n        ui_gdi.line(x + radius, b, r - radius + 1, b, border);\n        ui_gdi.line(x - 1, y + radius, x - 1, b - radius + 1, border);\n        ui_gdi.line(r + 1, y + radius, r + 1, b - radius + 1, border);\n    }\n}\n\nstatic void ui_gdi_rounded(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t border, ui_color_t fill) {\n    rt_swear(!ui_color_is_transparent(border) || !ui_color_is_transparent(fill));\n    if (!ui_color_is_transparent(fill)) {\n        ui_gdi_fill_rounded(x, y, w, h, radius, fill);\n    }\n    if (!ui_color_is_transparent(border)) {\n        ui_gdi_rounded_border(x, y, w, h, radius, border);\n    }\n}\n\nstatic void ui_gdi_gradient(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t rgba_from, ui_color_t rgba_to, bool vertical) {\n    TRIVERTEX vertex[2] = {0};\n    vertex[0].x = x;\n    vertex[0].y = y;\n    // TODO: colors:\n    vertex[0].Red   = (COLOR16)(((rgba_from >>  0) & 0xFF) << 8);\n    vertex[0].Green = (COLOR16)(((rgba_from >>  8) & 0xFF) << 8);\n    vertex[0].Blue  = (COLOR16)(((rgba_from >> 16) & 0xFF) << 8);\n    vertex[0].Alpha = (COLOR16)(((rgba_from >> 24) & 0xFF) << 8);\n    vertex[1].x = x + w;\n    vertex[1].y = y + h;\n    vertex[1].Red   = (COLOR16)(((rgba_to >>  0) & 0xFF) << 8);\n    vertex[1].Green = (COLOR16)(((rgba_to >>  8) & 0xFF) << 8);\n    vertex[1].Blue  = (COLOR16)(((rgba_to >> 16) & 0xFF) << 8);\n    vertex[1].Alpha = (COLOR16)(((rgba_to >> 24) & 0xFF) << 8);\n    GRADIENT_RECT gRect = {0, 1};\n    const uint32_t mode = vertical ?\n        GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H;\n    GradientFill(ui_gdi_hdc(), vertex, 2, &gRect, 1, mode);\n}\n\nstatic BITMAPINFO* ui_gdi_greyscale_bitmap_info(void) {\n    typedef struct bitmap_rgb_s {\n        BITMAPINFO bi;\n        RGBQUAD rgb[256];\n    } bitmap_rgb_t;\n    static bitmap_rgb_t storage; // for gs palette\n    static BITMAPINFO* bi = &storage.bi;\n    BITMAPINFOHEADER* bih = &bi->bmiHeader;\n    if (bih->biSize == 0) { // once\n        bih->biSize = sizeof(BITMAPINFOHEADER);\n        for (int32_t i = 0; i < 256; i++) {\n            RGBQUAD* q = &bi->bmiColors[i];\n            q->rgbReserved = 0;\n            q->rgbBlue = q->rgbGreen = q->rgbRed = (uint8_t)i;\n        }\n        bih->biPlanes = 1;\n        bih->biBitCount = 8;\n        bih->biCompression = BI_RGB;\n        bih->biClrUsed = 256;\n        bih->biClrImportant = 256;\n    }\n    return bi;\n}\n\nstatic void ui_gdi_pixels(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        int32_t bpp, const uint8_t* pixels) {\n    if (bpp == 1) {\n        ui_gdi.greyscale(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else if (bpp == 3) {\n        ui_gdi.bgr(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else if (bpp == 4) {\n        ui_gdi.bgrx(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else {\n        rt_fatal(\"bpp: %d not {1, 3, 4}\", bpp);\n    }\n}\n\nstatic void ui_gdi_greyscale(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFO *bi = ui_gdi_greyscale_bitmap_info(); // global! not thread safe\n        BITMAPINFOHEADER* bih = &bi->bmiHeader;\n        bih->biWidth = width;\n        bih->biHeight = -height; // top down image\n        bih->biSizeImage = (DWORD)(iw * abs(ih));\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n                    pixels, bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic BITMAPINFOHEADER ui_gdi_bgrx_init_bi(int32_t w, int32_t h, int32_t bpp) {\n    rt_assert(w > 0 && h >= 0); // h cannot be negative?\n    BITMAPINFOHEADER bi = {\n        .biSize = sizeof(BITMAPINFOHEADER),\n        .biPlanes = 1,\n        .biBitCount = (uint16_t)(bpp * 8),\n        .biCompression = BI_RGB,\n        .biWidth = w,\n        .biHeight = -h, // top down image\n        .biSizeImage = (DWORD)(w * abs(h) * bpp),\n        .biClrUsed = 0,\n        .biClrImportant = 0\n   };\n   return bi;\n}\n\n// bgr(width) assumes strides are padded and rounded up to 4 bytes\n// if this is not the case use ui_gdi.bitmap_init() that will unpack\n// and align scanlines prior to draw\n\nstatic void ui_gdi_bgr(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width * 3 + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFOHEADER bi = ui_gdi_bgrx_init_bi(width, height, 3);\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n                    pixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic void ui_gdi_bgrx(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width * 4 + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFOHEADER bi = ui_gdi_bgrx_init_bi(width, height, 4);\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n            pixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic BITMAPINFO* ui_gdi_init_bitmap_info(int32_t w, int32_t h, int32_t bpp,\n        BITMAPINFO* bi) {\n    rt_assert(w > 0 && h >= 0); // h cannot be negative?\n    bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\n    bi->bmiHeader.biWidth = w;\n    bi->bmiHeader.biHeight = -h;  // top down image\n    bi->bmiHeader.biPlanes = 1;\n    bi->bmiHeader.biBitCount = (uint16_t)(bpp * 8);\n    bi->bmiHeader.biCompression = BI_RGB;\n    bi->bmiHeader.biSizeImage = (DWORD)(w * abs(h) * bpp);\n    return bi;\n}\n\nstatic void ui_gdi_create_dib_section(ui_bitmap_t* image, int32_t w, int32_t h,\n        int32_t bpp) {\n    rt_fatal_if(image->texture != null, \"bitmap_dispose() not called?\");\n    // not using GetWindowDC(ui_app.window) will allow to initialize images\n    // before window is created\n    HDC c = CreateCompatibleDC(null); // GetWindowDC(ui_app.window);\n    BITMAPINFO local = { {sizeof(BITMAPINFOHEADER)} };\n    BITMAPINFO* bi = bpp == 1 ? ui_gdi_greyscale_bitmap_info() : &local;\n    image->texture = (ui_texture_t)CreateDIBSection(c,\n            ui_gdi_init_bitmap_info(w, h, bpp, bi),\n            DIB_RGB_COLORS, &image->pixels, null, 0x0\n    );\n    rt_fatal_if(image->texture == null || image->pixels == null);\n    rt_fatal_win32err(DeleteDC(c));\n}\n\nstatic void ui_gdi_bitmap_init_rgbx(ui_bitmap_t* image, int32_t w, int32_t h,\n        int32_t bpp, const uint8_t* pixels) {\n    bool swapped = bpp < 0;\n    bpp = abs(bpp);\n    rt_fatal_if(bpp != 4, \"bpp: %d\", bpp);\n    ui_gdi_create_dib_section(image, w, h, bpp);\n    const int32_t stride = (w * bpp + 3) & ~0x3;\n    uint8_t* scanline = image->pixels;\n    const uint8_t* rgbx = pixels;\n    if (!swapped) {\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgra[0] = rgbx[2];\n                bgra[1] = rgbx[1];\n                bgra[2] = rgbx[0];\n                bgra[3] = 0xFF;\n                bgra += 4;\n                rgbx += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    } else {\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgra[0] = rgbx[0];\n                bgra[1] = rgbx[1];\n                bgra[2] = rgbx[2];\n                bgra[3] = 0xFF;\n                bgra += 4;\n                rgbx += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    }\n    image->w = w;\n    image->h = h;\n    image->bpp = bpp;\n    image->stride = stride;\n}\n\nstatic void ui_gdi_bitmap_init(ui_bitmap_t* image, int32_t w, int32_t h, int32_t bpp,\n        const uint8_t* pixels) {\n    bool swapped = bpp < 0;\n    bpp = abs(bpp);\n    rt_fatal_if(bpp < 0 || bpp == 2 || bpp > 4, \"bpp=%d not {1, 3, 4}\", bpp);\n    ui_gdi_create_dib_section(image, w, h, bpp);\n    // Win32 bitmaps stride is rounded up to 4 bytes\n    const int32_t stride = (w * bpp + 3) & ~0x3;\n    uint8_t* scanline = image->pixels;\n    if (bpp == 1) {\n        for (int32_t y = 0; y < h; y++) {\n            memcpy(scanline, pixels, (size_t)w);\n            pixels += w;\n            scanline += stride;\n        }\n    } else if (bpp == 3 && !swapped) {\n        const uint8_t* rgb = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgr = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgr[0] = rgb[2];\n                bgr[1] = rgb[1];\n                bgr[2] = rgb[0];\n                bgr += 3;\n                rgb += 3;\n            }\n            pixels += w * bpp;\n            scanline += stride;\n        }\n    } else if (bpp == 3 && swapped) {\n        const uint8_t* rgb = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgr = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgr[0] = rgb[0];\n                bgr[1] = rgb[1];\n                bgr[2] = rgb[2];\n                bgr += 3;\n                rgb += 3;\n            }\n            pixels += w * bpp;\n            scanline += stride;\n        }\n    } else if (bpp == 4 && !swapped) {\n        // premultiply alpha, see:\n        // https://stackoverflow.com/questions/24595717/alphablend-generating-incorrect-colors\n        const uint8_t* rgba = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                int32_t alpha = rgba[3];\n                bgra[0] = (uint8_t)(rgba[2] * alpha / 255);\n                bgra[1] = (uint8_t)(rgba[1] * alpha / 255);\n                bgra[2] = (uint8_t)(rgba[0] * alpha / 255);\n                bgra[3] = rgba[3];\n                bgra += 4;\n                rgba += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    } else if (bpp == 4 && swapped) {\n        // premultiply alpha, see:\n        // https://stackoverflow.com/questions/24595717/alphablend-generating-incorrect-colors\n        const uint8_t* rgba = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                int32_t alpha = rgba[3];\n                bgra[0] = (uint8_t)(rgba[0] * alpha / 255);\n                bgra[1] = (uint8_t)(rgba[1] * alpha / 255);\n                bgra[2] = (uint8_t)(rgba[2] * alpha / 255);\n                bgra[3] = rgba[3];\n                bgra += 4;\n                rgba += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    }\n    image->w = w;\n    image->h = h;\n    image->bpp = bpp;\n    image->stride = stride;\n}\n\nstatic void ui_gdi_alpha(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* image, fp64_t alpha) {\n    rt_assert(image->bpp > 0);\n    rt_assert(0 <= alpha && alpha <= 1);\n    rt_not_null(ui_gdi_hdc());\n    HDC c = CreateCompatibleDC(ui_gdi_hdc());\n    rt_not_null(c);\n    HBITMAP zero1x1 = SelectBitmap((HDC)c, (HBITMAP)image->texture);\n    BLENDFUNCTION bf = { 0 };\n    bf.SourceConstantAlpha = (uint8_t)(0xFF * alpha + 0.49);\n    if (image->bpp == 4) {\n        bf.BlendOp = AC_SRC_OVER;\n        bf.BlendFlags = 0;\n        bf.AlphaFormat = AC_SRC_ALPHA;\n    } else {\n        bf.BlendOp = AC_SRC_OVER;\n        bf.BlendFlags = 0;\n        bf.AlphaFormat = 0;\n    }\n    rt_assert(0 <= ix && ix < image->w && 0 <= iy && iy < image->h);\n    rt_assert(ix + iw <= image->w && iy + ih <= image->h);\n    rt_fatal_win32err(AlphaBlend(ui_gdi_hdc(), dx, dy, dw, dh,\n                                 c, ix, iy, iw, ih, bf));\n    SelectBitmap((HDC)c, zero1x1);\n    rt_fatal_win32err(DeleteDC(c));\n}\n\nstatic void ui_gdi_bitmap(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* image) {\n    rt_assert(image->bpp == 1 || image->bpp == 3 || image->bpp == 4);\n    rt_assert(0 <= ix && ix < image->w && 0 <= iy && iy < image->h);\n    rt_assert(ix + iw <= image->w && iy + ih <= image->h);\n    rt_not_null(ui_gdi_hdc());\n    if (image->bpp == 1) { // StretchBlt() is bad for greyscale\n        BITMAPINFO* bi   = ui_gdi_greyscale_bitmap_info();\n        BITMAPINFO* info = ui_gdi_init_bitmap_info(image->w, image->h, 1, bi);\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh, ix, iy, iw, ih,\n            image->pixels, info, DIB_RGB_COLORS, SRCCOPY) == 0);\n    } else {\n        HDC c = CreateCompatibleDC(ui_gdi_hdc());\n        rt_not_null(c);\n        HBITMAP zero1x1 = SelectBitmap(c, image->texture);\n        rt_fatal_win32err(StretchBlt(ui_gdi_hdc(), dx, dy, dw, dh,\n            c, ix, iy, iw, ih, SRCCOPY));\n        SelectBitmap(c, zero1x1);\n        rt_fatal_win32err(DeleteDC(c));\n    }\n}\n\nstatic void ui_gdi_icon(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_icon_t icon) {\n    DrawIconEx(ui_gdi_hdc(), x, y, (HICON)icon, w, h, 0, NULL, DI_NORMAL | DI_COMPAT);\n}\n\nstatic void ui_gdi_cleartype(bool on) {\n    enum { spif = SPIF_UPDATEINIFILE | SPIF_SENDCHANGE };\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHING,\n                                                   true, 0, spif));\n    uintptr_t s = on ? FE_FONTSMOOTHINGCLEARTYPE : FE_FONTSMOOTHINGSTANDARD;\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHINGTYPE,\n        0, (void*)s, spif));\n}\n\nstatic void ui_gdi_font_smoothing_contrast(int32_t c) {\n    rt_fatal_if(!(c == -1 || 1000 <= c && c <= 2200), \"contrast: %d\", c);\n    if (c == -1) { c = 1400; }\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHINGCONTRAST,\n        0, (void*)(uintptr_t)c, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE));\n}\n\nrt_static_assertion(ui_gdi_font_quality_default == DEFAULT_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_draft == DRAFT_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_proof == PROOF_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_nonantialiased == NONANTIALIASED_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_antialiased == ANTIALIASED_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_cleartype == CLEARTYPE_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_cleartype_natural == CLEARTYPE_NATURAL_QUALITY);\n\nstatic ui_font_t ui_gdi_create_font(const char* family, int32_t h, int32_t q) {\n    rt_assert(h > 0);\n    LOGFONTA lf = {0};\n    int32_t n = GetObjectA(ui_app.fm.prop.normal.font, sizeof(lf), &lf);\n    rt_fatal_if(n != (int32_t)sizeof(lf));\n    lf.lfHeight = -h;\n    rt_str_printf(lf.lfFaceName, \"%s\", family);\n    if (ui_gdi_font_quality_default <= q &&\n        q <= ui_gdi_font_quality_cleartype_natural) {\n        lf.lfQuality = (uint8_t)q;\n    } else {\n        rt_fatal_if(q != -1, \"use -1 for do not care quality\");\n    }\n    return (ui_font_t)CreateFontIndirectA(&lf);\n}\n\nstatic ui_font_t ui_gdi_font(ui_font_t f, int32_t h, int32_t q) {\n    rt_assert(f != null && h > 0);\n    LOGFONTA lf = {0};\n    int32_t n = GetObjectA(f, sizeof(lf), &lf);\n    rt_fatal_if(n != (int32_t)sizeof(lf));\n    lf.lfHeight = -h;\n    if (ui_gdi_font_quality_default <= q &&\n        q <= ui_gdi_font_quality_cleartype_natural) {\n        lf.lfQuality = (uint8_t)q;\n    } else {\n        rt_fatal_if(q != -1, \"use -1 for do not care quality\");\n    }\n    return (ui_font_t)CreateFontIndirectA(&lf);\n}\n\nstatic void ui_gdi_delete_font(ui_font_t f) {\n    rt_fatal_win32err(DeleteFont(f));\n}\n\n// guaranteed to return dc != null even if not painting\n\nstatic HDC ui_gdi_get_dc(void) {\n    rt_not_null(ui_app.window);\n    HDC hdc = ui_gdi_hdc() != null ?\n              ui_gdi_hdc() : GetDC((HWND)ui_app.window);\n    rt_not_null(hdc);\n    return hdc;\n}\n\nstatic void ui_gdi_release_dc(HDC hdc) {\n    if (ui_gdi_hdc() == null) {\n        ReleaseDC((HWND)ui_app.window, hdc);\n    }\n}\n\n#define ui_gdi_with_hdc(code) do {           \\\n    HDC hdc = ui_gdi_get_dc();               \\\n    code                                     \\\n    ui_gdi_release_dc(hdc);                  \\\n} while (0)\n\n#define ui_gdi_hdc_with_font(f, ...) do {    \\\n    rt_not_null(f);                          \\\n    HDC hdc = ui_gdi_get_dc();               \\\n    HFONT font_ = SelectFont(hdc, (HFONT)f); \\\n    { __VA_ARGS__ }                          \\\n    SelectFont(hdc, font_);                  \\\n    ui_gdi_release_dc(hdc);                  \\\n} while (0)\n\nstatic void ui_gdi_dump_hdc_fm(HDC hdc) {\n    // https://en.wikipedia.org/wiki/Quad_(typography)\n    // https://learn.microsoft.com/en-us/windows/win32/gdi/string-widths-and-heights\n    // https://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-font\n    // Amazingly same since Windows 3.1 1992\n    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-textmetrica\n    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-outlinetextmetrica\n    TEXTMETRICA tm = {0};\n    rt_fatal_win32err(GetTextMetricsA(hdc, &tm));\n    char pitch[64] = { 0 };\n    if (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) { strcat(pitch, \"FIXED_PITCH \"); }\n    if (tm.tmPitchAndFamily & TMPF_VECTOR)      { strcat(pitch, \"VECTOR \"); }\n    if (tm.tmPitchAndFamily & TMPF_DEVICE)      { strcat(pitch, \"DEVICE \"); }\n    if (tm.tmPitchAndFamily & TMPF_TRUETYPE)    { strcat(pitch, \"TRUETYPE \"); }\n    rt_println(\"tm: .pitch_and_family: %s\", pitch);\n    rt_println(\".height            : %2d   .ascent (baseline) : %2d  .descent: %2d\",\n            tm.tmHeight, tm.tmAscent, tm.tmDescent);\n    rt_println(\".internal_leading  : %2d   .external_leading  : %2d  .ave_char_width: %2d\",\n            tm.tmInternalLeading, tm.tmExternalLeading, tm.tmAveCharWidth);\n    rt_println(\".max_char_width    : %2d   .weight            : %2d .overhang: %2d\",\n            tm.tmMaxCharWidth, tm.tmWeight, tm.tmOverhang);\n    rt_println(\".digitized_aspect_x: %2d   .digitized_aspect_y: %2d\",\n            tm.tmDigitizedAspectX, tm.tmDigitizedAspectY);\n    rt_swear(tm.tmPitchAndFamily & TMPF_TRUETYPE);\n    OUTLINETEXTMETRICA otm = { .otmSize = sizeof(OUTLINETEXTMETRICA) };\n    uint32_t bytes = GetOutlineTextMetricsA(hdc, otm.otmSize, &otm);\n    rt_swear(bytes == sizeof(OUTLINETEXTMETRICA));\n    // unsupported XHeight CapEmHeight\n    // ignored:    MacDescent, MacLineGap, EMSquare, ItalicAngle\n    //             CharSlopeRise, CharSlopeRun, ItalicAngle\n    rt_println(\"otm: .Ascent       : %2d   .Descent        : %2d\",\n            otm.otmAscent, otm.otmDescent);\n    rt_println(\".otmLineGap        : %2u\", otm.otmLineGap);\n    rt_println(\".FontBox.ltrb      :  %d,%d %2d,%2d\",\n            otm.otmrcFontBox.left, otm.otmrcFontBox.top,\n            otm.otmrcFontBox.right, otm.otmrcFontBox.bottom);\n    rt_println(\".MinimumPPEM       : %2u    (minimum height in pixels)\",\n            otm.otmusMinimumPPEM);\n    rt_println(\".SubscriptOffset   : %d,%d  .SubscriptSize.x   : %dx%d\",\n            otm.otmptSubscriptOffset.x, otm.otmptSubscriptOffset.y,\n            otm.otmptSubscriptSize.x, otm.otmptSubscriptSize.y);\n    rt_println(\".SuperscriptOffset : %d,%d  .SuperscriptSize.x : %dx%d\",\n            otm.otmptSuperscriptOffset.x, otm.otmptSuperscriptOffset.y,\n            otm.otmptSuperscriptSize.x,   otm.otmptSuperscriptSize.y);\n    rt_println(\".UnderscoreSize    : %2d   .UnderscorePosition: %2d\",\n            otm.otmsUnderscoreSize, otm.otmsUnderscorePosition);\n    rt_println(\".StrikeoutSize     : %2u   .StrikeoutPosition : %2d \",\n            otm.otmsStrikeoutSize,  otm.otmsStrikeoutPosition);\n    int32_t h = otm.otmAscent + abs(tm.tmDescent); // without diacritical space above\n    fp32_t pts = (h * 72.0f)  / GetDeviceCaps(hdc, LOGPIXELSY);\n    rt_println(\"height: %.1fpt\", pts);\n}\n\nstatic void ui_gdi_dump_fm(ui_font_t f) {\n    rt_not_null(f);\n    ui_gdi_hdc_with_font(f, { ui_gdi_dump_hdc_fm(hdc); });\n}\n\nstatic void ui_gdi_get_fm(HDC hdc, ui_fm_t* fm) {\n    TEXTMETRICA tm = {0};\n    rt_fatal_win32err(GetTextMetricsA(hdc, &tm));\n    rt_swear(tm.tmPitchAndFamily & TMPF_TRUETYPE);\n    OUTLINETEXTMETRICA otm = { .otmSize = sizeof(OUTLINETEXTMETRICA) };\n    uint32_t bytes = GetOutlineTextMetricsA(hdc, otm.otmSize, &otm);\n    rt_swear(bytes == sizeof(OUTLINETEXTMETRICA));\n    // \"tm.tmAscent\" The ascent (units above the base line) of characters\n    // and actually is \"baseline\" in other terminology\n    // \"otm.otmAscent\" The maximum distance characters in this font extend\n    // above the base line. This is the typographic ascent for the font.\n    // otm.otmEMSquare usually is 2048 which is size of rasterizer\n    fm->height   = tm.tmHeight;\n    fm->baseline = tm.tmAscent;\n    fm->ascent   = otm.otmAscent;\n    fm->descent  = tm.tmDescent;\n    fm->baseline = tm.tmAscent;\n    fm->x_height = otm.otmsXHeight;\n    fm->cap_em_height = otm.otmsCapEmHeight;\n    fm->internal_leading = tm.tmInternalLeading;\n    fm->external_leading = tm.tmExternalLeading;\n    fm->average_char_width = tm.tmAveCharWidth;\n    fm->max_char_width = tm.tmMaxCharWidth;\n    fm->line_gap = otm.otmLineGap;\n    fm->subscript.w = otm.otmptSubscriptSize.x;\n    fm->subscript.h = otm.otmptSubscriptSize.y;\n    fm->subscript_offset.x = otm.otmptSubscriptOffset.x;\n    fm->subscript_offset.y = otm.otmptSubscriptOffset.y;\n    fm->superscript.w = otm.otmptSuperscriptSize.x;\n    fm->superscript.h = otm.otmptSuperscriptSize.y;\n    fm->superscript_offset.x = otm.otmptSuperscriptOffset.x;\n    fm->superscript_offset.y = otm.otmptSuperscriptOffset.y;\n    fm->underscore = otm.otmsUnderscoreSize;\n    fm->underscore_position = otm.otmsUnderscorePosition;\n    fm->strike_through = otm.otmsStrikeoutSize;\n    fm->strike_through_position = otm.otmsStrikeoutPosition;\n    fm->design_units_per_em = (int)otm.otmEMSquare;\n    fm->box = (ui_rect_t){\n                otm.otmrcFontBox.left, otm.otmrcFontBox.top,\n                otm.otmrcFontBox.right - otm.otmrcFontBox.left,\n                otm.otmrcFontBox.top - otm.otmrcFontBox.bottom // inverted\n    };\n    // otm.Descent: The maximum distance characters in this font extend below\n    // the base line. This is the typographic descent for the font.\n    // Negative from the bottom (font.height)\n    // tm.Descent: The descent (units below the base line) of characters.\n    // Positive from the baseline down\n    rt_assert(tm.tmDescent >= 0 && otm.otmDescent <= 0 &&\n           -otm.otmDescent <= tm.tmDescent,\n           \"tm.tmDescent: %d otm.otmDescent: %d\", tm.tmDescent, otm.otmDescent);\n    // \"Mac\" typography is ignored because it's usefulness is unclear.\n    // Italic angle/slant/run is ignored because at the moment edit\n    // view implementation does not support italics and thus does not\n    // need it. Easy to add if necessary.\n}\n\nstatic void ui_gdi_update_fm(ui_fm_t* fm, ui_font_t f) {\n    rt_not_null(f);\n    SIZE em = {0, 0}; // \"m\"\n    *fm = (ui_fm_t){ .font = f };\n//  ui_gdi.dump_fm(f);\n    ui_gdi_hdc_with_font(f, {\n        ui_gdi_get_fm(hdc, fm);\n        // rt_glyph_nbsp and \"M\" have the same result\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"m\", 1, &em));\n        SIZE vl = {0}; // \"|\" Vertical Line https://www.compart.com/en/unicode/U+007C\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"|\", 1, &vl));\n        SIZE e3 = {0}; // Three-Em Dash\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc,\n            rt_glyph_three_em_dash, 1, &e3));\n        fm->mono = em.cx == vl.cx && vl.cx == e3.cx;\n//      rt_println(\"vl: %d %d\", vl.cx, vl.cy);\n//      rt_println(\"e3: %d %d\", e3.cx, e3.cy);\n//      rt_println(\"fm->mono: %d height: %d baseline: %d ascent: %d descent: %d\",\n//              fm->mono, fm->height, fm->baseline, fm->ascent, fm->descent);\n    });\n    rt_assert(fm->baseline <= fm->height);\n    fm->em = (ui_wh_t){ .w = fm->height, .h = fm->height };\n//  rt_println(\"fm.em: %dx%d\", fm->em.w, fm->em.h);\n}\n\nstatic int32_t ui_gdi_draw_utf16(ui_font_t font, const char* s, int32_t n,\n        RECT* r, uint32_t format) { // ~70 microsecond Core i-7 3667U 2.0 GHz (2012)\n    // if font == null, draws on HDC with selected font\nif (0) {\n    HDC hdc = ui_gdi_hdc();\n    if (hdc != null) {\n        SIZE em = {0, 0}; // \"M\"\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"M\", 1, &em));\n        rt_println(\"em: %d %d\", em.cx, em.cy);\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, rt_glyph_em_quad, 1, &em));\n        rt_println(\"em: %d %d\", em.cx, em.cy);\n        SIZE vl = {0}; // \"|\" Vertical Line https://www.compart.com/en/unicode/U+007C\n        SIZE e3 = {0}; // Three-Em Dash\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"|\", 1, &vl));\n        rt_println(\"vl: %d %d\", vl.cx, vl.cy);\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, rt_glyph_three_em_dash, 1, &e3));\n        rt_println(\"e3: %d %d\", e3.cx, e3.cy);\n    }\n}\n    int32_t count = rt_str.utf16_chars(s, -1);\n    rt_assert(0 < count && count < 4096, \"be reasonable count: %d?\", count);\n    uint16_t ws[4096];\n    rt_swear(count <= rt_countof(ws), \"find another way to draw!\");\n    rt_str.utf8to16(ws, count, s, -1);\n    int32_t h = 0; // return value is the height of the text\n    if (font != null) {\n        ui_gdi_hdc_with_font(font, { h = DrawTextW(hdc, ws, n, r, format); });\n    } else { // with already selected font\n        ui_gdi_with_hdc({ h = DrawTextW(hdc, ws, n, r, format); });\n    }\n    return h;\n}\n\ntypedef struct { // draw text parameters\n    const ui_fm_t* fm;\n    const char* format; // format string\n    va_list va;\n    RECT rc;\n    uint32_t flags; // flags:\n    // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-drawtextw\n    // DT_CALCRECT DT_NOCLIP useful for measure\n    // DT_END_ELLIPSIS useful for clipping\n    // DT_LEFT, DT_RIGHT, DT_CENTER useful for paragraphs\n    // DT_WORDBREAK is not good (GDI does not break nicely)\n    // DT_BOTTOM, DT_VCENTER limited usability in weird cases (layout is better)\n    // DT_NOPREFIX not to draw underline at \"&Keyboard shortcuts\n    // DT_SINGLELINE versus multiline\n} ui_gdi_dtp_t;\n\nstatic void ui_gdi_text_draw(ui_gdi_dtp_t* p) {\n    rt_not_null(p);\n    char text[4096]; // expected to be enough for single text draw\n    text[0] = 0;\n    rt_str.format_va(text, rt_countof(text), p->format, p->va);\n    text[rt_countof(text) - 1] = 0;\n    int32_t k = (int32_t)rt_str.len(text);\n    if (k > 0) {\n        rt_swear(k > 0 && k < rt_countof(text), \"k=%d n=%d fmt=%s\", k, p->format);\n        // rectangle is always calculated - it makes draw text\n        // much slower but UI layer is mostly uses bitmap caching:\n        if ((p->flags & DT_CALCRECT) == 0) {\n            // no actual drawing just calculate rectangle\n            bool b = ui_gdi_draw_utf16(p->fm->font, text, -1, &p->rc, p->flags | DT_CALCRECT);\n            rt_assert(b, \"text_utf16(%s) failed\", text); (void)b;\n        }\n        bool b = ui_gdi_draw_utf16(p->fm->font, text, -1, &p->rc, p->flags);\n        rt_assert(b, \"text_utf16(%s) failed\", text); (void)b;\n    } else {\n        p->rc.right = p->rc.left;\n        p->rc.bottom = p->rc.top + p->fm->height;\n    }\n}\n\nenum {\n    sl_draw          = DT_LEFT|DT_NOCLIP|DT_SINGLELINE|DT_NOCLIP,\n    sl_measure       = sl_draw|DT_CALCRECT,\n    ml_draw_break    = DT_LEFT|DT_NOPREFIX|DT_NOCLIP|DT_NOFULLWIDTHCHARBREAK|\n                       DT_WORDBREAK,\n    ml_measure_break = ml_draw_break|DT_CALCRECT,\n    ml_draw          = DT_LEFT|DT_NOPREFIX|DT_NOCLIP|DT_NOFULLWIDTHCHARBREAK,\n    ml_measure       = ml_draw|DT_CALCRECT\n};\n\nstatic ui_wh_t ui_gdi_text_with_flags(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w,\n        const char* format, va_list va, uint32_t flags) {\n    const int32_t right = w == 0 ? 0 : x + w;\n    ui_gdi_dtp_t p = {\n        .fm = ta->fm,\n        .format = format,\n        .va = va,\n        .rc = {.left = x, .top = y, .right = right, .bottom = 0 },\n        .flags = flags\n    };\n    ui_color_t c = ta->color;\n    if (!ta->measure) {\n        if (ui_color_is_undefined(c)) {\n            rt_swear(ta->color_id > 0);\n            c = ui_colors.get_color(ta->color_id);\n        } else {\n            rt_swear(ta->color_id == 0);\n        }\n        c = ui_gdi_set_text_color(c);\n    }\n    ui_gdi_text_draw(&p);\n    if (!ta->measure) { ui_gdi_set_text_color(c); } // restore color\n    return (ui_wh_t){ p.rc.right - p.rc.left, p.rc.bottom - p.rc.top };\n}\n\nstatic ui_wh_t ui_gdi_text_va(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y,  const char* format, va_list va) {\n    const uint32_t flags = sl_draw | (ta->measure ? sl_measure : 0);\n    return ui_gdi_text_with_flags(ta, x, y, 0, format, va, flags);\n}\n\nstatic ui_wh_t ui_gdi_text(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, const char* format, ...) {\n    const uint32_t flags = sl_draw | (ta->measure ? sl_measure : 0);\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_gdi_text_with_flags(ta, x, y, 0, format, va, flags);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_gdi_multiline_va(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w, const char* format, va_list va) {\n    const uint32_t flags = ta->measure ?\n                            (w <= 0 ? ml_measure : ml_measure_break) :\n                            (w <= 0 ? ml_draw    : ml_draw_break);\n    return ui_gdi_text_with_flags(ta, x, y, w, format, va, flags);\n}\n\nstatic ui_wh_t ui_gdi_multiline(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_gdi_multiline_va(ta, x, y, w, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_gdi_glyphs_placement(const ui_gdi_ta_t* ta,\n        const char* utf8, int32_t bytes, int32_t x[], int32_t glyphs) {\n    rt_swear(bytes >= 0 && glyphs >= 0 && glyphs <= bytes);\n    rt_assert(false, \"Does not work for Tamil simplest utf8: \\xe0\\xae\\x9a utf16: 0x0B9A\");\n    x[0] = 0;\n    ui_wh_t wh = { .w = 0, .h = 0 };\n    if (bytes > 0) {\n        const int32_t chars = rt_str.utf16_chars(utf8, bytes);\n        uint16_t* utf16 = rt_stackalloc((chars + 1) * sizeof(uint16_t));\n        uint16_t* output = rt_stackalloc((chars + 1) * sizeof(uint16_t));\n        const errno_t r = rt_str.utf8to16(utf16, chars, utf8, bytes);\n        rt_swear(r == 0);\n// TODO: remove\n#if 1\n        char str[16 * 1024] = {0};\n        char hex[16 * 1024] = {0};\n        for (int i = 0; i < chars; i++) {\n            rt_str_printf(hex, \"%04X \", utf16[i]);\n            strcat(str, hex);\n        }\nrt_println(\"%.*s %s %p bytes:%d glyphs:%d font:%p hdc:%p\", bytes, utf8, str, utf8, bytes, glyphs, ta->fm->font, ui_gdi_context.hdc);\n#endif\n        GCP_RESULTSW gcp = {\n            .lStructSize = sizeof(GCP_RESULTSW),\n            .lpOutString = output,\n            .nGlyphs = glyphs\n        };\n        gcp.lpDx = (int*)rt_stackalloc((chars + 1) * sizeof(int));\n        DWORD n = 0;\n        const int mx = INT32_MAX; // max extent\n        const DWORD f = GCP_MAXEXTENT; // |GCP_GLYPHSHAPE|GCP_DIACRITIC|GCP_LIGATE\n        if (ta->fm->font != null) {\n            ui_gdi_hdc_with_font(ta->fm->font, {\n                n = GetCharacterPlacementW(hdc, utf16, chars, mx, &gcp, f);\n            });\n        } else { // with already selected font\n            ui_gdi_with_hdc({\n                n = GetCharacterPlacementW(hdc, utf16, chars, mx, &gcp, f);\n            });\n        }\n        wh = (ui_wh_t){ .w = LOWORD(n), .h = HIWORD(n) };\n        if (n != 0) {\n            // IS_HIGH_SURROGATE(wch)\n            // IS_LOW_SURROGATE(wch)\n            // IS_SURROGATE_PAIR(hs, ls)\n            int32_t i = 0;\n            int32_t k = 1;\n            while (i < chars) {\n                x[k] = x[k - 1] + gcp.lpDx[i];\n//              rt_println(\"%d\", x[i]);\n                k++;\n                if (i < chars - 1 && rt_str.utf16_is_high_surrogate(utf16[i]) &&\n                                     rt_str.utf16_is_low_surrogate(utf16[i + 1])) {\n                    i += 2;\n                } else {\n                    i++;\n                }\n            }\n            rt_assert(k == glyphs + 1);\n        } else {\n//          rt_assert(false, \"GetCharacterPlacementW() failed\");\n            rt_println(\"GetCharacterPlacementW() failed\");\n        }\n    }\n    return wh;\n}\n\n// to enable load_bitmap() function\n// 1. Add\n//    curl.exe https://raw.githubusercontent.com/nothings/stb/master/stb_bitmap.h stb_bitmap.h\n//    to the project precompile build step\n// 2. After\n//    #define ui_implementation\n//    include \"ui/ui.h\"\n//    add\n//    #define STBI_ASSERT(x) assert(x)\n//    #define STB_bitmap_IMPLEMENTATION\n//    #include \"stb_bitmap.h\"\n\nstatic uint8_t* ui_gdi_load_bitmap(const void* data, int32_t bytes, int* w, int* h,\n        int* bytes_per_pixel, int32_t preferred_bytes_per_pixel) {\n    #ifdef STBI_VERSION\n        return stbi_load_from_memory((uint8_t const*)data, bytes, w, h,\n            bytes_per_pixel, preferred_bytes_per_pixel);\n    #else // see instructions above\n        (void)data; (void)bytes; (void)data; (void)w; (void)h;\n        (void)bytes_per_pixel; (void)preferred_bytes_per_pixel;\n        rt_fatal_if(true, \"curl.exe --silent --fail --create-dirs \"\n            \"https://raw.githubusercontent.com/nothings/stb/master/stb_bitmap.h \"\n            \"--output ext/stb_bitmap.h\");\n        return null;\n    #endif\n}\n\nstatic void ui_gdi_bitmap_dispose(ui_bitmap_t* image) {\n    rt_fatal_win32err(DeleteBitmap(image->texture));\n    memset(image, 0, sizeof(ui_bitmap_t));\n}\n\nui_gdi_if ui_gdi = {\n    .ta = {\n        .prop = {\n            .normal = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.normal,\n                .measure  = false\n            },\n            .title = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.title,\n                .measure  = false\n            },\n            .rubric = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.rubric,\n                .measure  = false\n            },\n            .H1 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H1,\n                .measure  = false\n            },\n            .H2 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H2,\n                .measure  = false\n            },\n            .H3 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H3,\n                .measure  = false\n            }\n        },\n        .mono = {\n            .normal = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.normal,\n                .measure  = false\n            },\n            .title = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.title,\n                .measure  = false\n            },\n            .rubric = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.rubric,\n                .measure  = false\n            },\n            .H1 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H1,\n                .measure  = false\n            },\n            .H2 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H2,\n                .measure  = false\n            },\n            .H3 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H3,\n                .measure  = false\n            }\n        },\n    },\n    .init                     = ui_gdi_init,\n    .begin                    = ui_gdi_begin,\n    .end                      = ui_gdi_end,\n    .color_rgb                = ui_gdi_color_rgb,\n    .bitmap_init              = ui_gdi_bitmap_init,\n    .bitmap_init_rgbx         = ui_gdi_bitmap_init_rgbx,\n    .bitmap_dispose           = ui_gdi_bitmap_dispose,\n    .alpha                    = ui_gdi_alpha,\n    .bitmap                   = ui_gdi_bitmap,\n    .icon                     = ui_gdi_icon,\n    .set_clip                 = ui_gdi_set_clip,\n    .pixel                    = ui_gdi_pixel,\n    .line                     = ui_gdi_line,\n    .frame                    = ui_gdi_frame,\n    .rect                     = ui_gdi_rect,\n    .fill                     = ui_gdi_fill,\n    .poly                     = ui_gdi_poly,\n    .circle                   = ui_gdi_circle,\n    .rounded                  = ui_gdi_rounded,\n    .gradient                 = ui_gdi_gradient,\n    .pixels                   = ui_gdi_pixels,\n    .greyscale                = ui_gdi_greyscale,\n    .bgr                      = ui_gdi_bgr,\n    .bgrx                     = ui_gdi_bgrx,\n    .cleartype                = ui_gdi_cleartype,\n    .font_smoothing_contrast  = ui_gdi_font_smoothing_contrast,\n    .create_font              = ui_gdi_create_font,\n    .font                     = ui_gdi_font,\n    .delete_font              = ui_gdi_delete_font,\n    .dump_fm                  = ui_gdi_dump_fm,\n    .update_fm                = ui_gdi_update_fm,\n    .text_va                  = ui_gdi_text_va,\n    .text                     = ui_gdi_text,\n    .multiline_va             = ui_gdi_multiline_va,\n    .multiline                = ui_gdi_multiline,\n    .glyphs_placement         = ui_gdi_glyphs_placement,\n    .fini                     = ui_gdi_fini\n};\n\n#pragma pop_macro(\"ui_gdi_hdc_with_font\")\n#pragma pop_macro(\"ui_gdi_with_hdc\")\n// ________________________________ ui_image.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic fp64_t ui_image_scale_of(int32_t nominator, int32_t denominator) {\n    const int32_t zn = 1 << (nominator - 1);\n    const int32_t zd = 1 << (denominator - 1);\n    return (fp64_t)zn / (fp64_t)zd;\n}\n\nstatic fp64_t ui_image_scale(ui_image_t* iv) {\n    if (iv->fit && iv->w > 0 && iv->h > 0) {\n        return min((fp64_t)iv->w / iv->image.w,\n                   (fp64_t)iv->h / iv->image.h);\n    } else if (iv->fill && iv->w > 0 && iv->h > 0) {\n        return max((fp64_t)iv->w / iv->image.w,\n                   (fp64_t)iv->h / iv->image.h);\n    } else {\n        return ui_image_scale_of(iv->zn, iv->zd);\n    }\n}\n\nstatic ui_rect_t ui_image_position(ui_image_t* iv) {\n    ui_rect_t rc = { 0, 0, 0, 0 };\n    if (iv->image.pixels != null) {\n        int32_t iw = iv->image.w;\n        int32_t ih = iv->image.h;\n        // zoomed image width and height\n        rc.w = (int32_t)((fp64_t)iw * ui_image.scale(iv));\n        rc.h = (int32_t)((fp64_t)ih * ui_image.scale(iv));\n        int32_t shift_x = (int32_t)((rc.w - iv->w) * iv->sx);\n        int32_t shift_y = (int32_t)((rc.h - iv->h) * iv->sy);\n        // shift_x and shift_y are in zoomed image coordinates\n        rc.x = iv->x - shift_x; // screen x\n        rc.y = iv->y - shift_y; // screen y\n    }\n    return rc;\n}\n\nstatic void ui_image_paint(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n//  ui_gdi.fill(v->x, v->y, v->w, v->h, ui_colors.black);\n    if (iv->image.pixels != null) {\n        ui_gdi.set_clip(v->x, v->y, v->w, v->h);\n        rt_swear(!iv->fit || !iv->fill, \"make up your mind\");\n        rt_swear(0 < iv->zn && iv->zn <= 16);\n        rt_swear(0 < iv->zd && iv->zd <= 16);\n        // only 1:2 and 2:1 etc are supported:\n        if (iv->zn != 1) { rt_swear(iv->zd == 1); }\n        if (iv->zd != 1) { rt_swear(iv->zn == 1); }\n        const int32_t iw = iv->image.w;\n        const int32_t ih = iv->image.h;\n        ui_rect_t rc = ui_image_position(iv);\n        if (iv->image.bpp == 1) {\n            ui_gdi.greyscale(rc.x, rc.y, rc.w, rc.h,\n                0, 0, iw, ih,\n                iw, ih, iv->image.stride,\n                iv->image.pixels);\n        } else if (iv->image.bpp == 3) {\n            ui_gdi.bgr(rc.x, rc.y, rc.w, rc.h,\n                         0, 0, iw, ih,\n                         iw, ih, iv->image.stride,\n                         iv->image.pixels);\n        } else if (iv->image.bpp == 4) {\n            if (iv->image.texture == null) {\n                ui_gdi.bgrx(rc.x, rc.y, rc.w, rc.h,\n                              0, 0, iw, ih,\n                              iw, ih, iv->image.stride,\n                              iv->image.pixels);\n            } else {\n                ui_gdi.alpha(rc.x, rc.y, rc.w, rc.h,\n                              0, 0, iw, ih,\n                              &iv->image, iv->alpha);\n            }\n        } else {\n            rt_swear(false, \"unsupported .c: %d\", iv->image.bpp);\n        }\n        if (ui_view.has_focus(v)) {\n            ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n            ui_gdi.frame(v->x, v->y, v->w, v->h, highlight);\n        }\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n}\n\nstatic void ui_image_tools_background(ui_view_t* v) {\n    ui_color_t face = ui_colors.get_color(ui_color_id_button_face);\n    ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n    ui_gdi.fill(v->x, v->y, v->w, v->h, face);\n    ui_gdi.frame(v->x, v->y, v->w, v->h, highlight);\n}\n\nstatic void ui_image_show_tools(ui_image_t* iv, bool show) {\n    if (iv->focusable) {\n        if (iv->tool.bar.state.hidden  != !show) {\n            iv->tool.bar.state.hidden   = !show;\n            iv->tool.bar.state.disabled = !show;\n            iv->tool.ratio.state.hidden = !show;\n            ui_app.request_layout();\n        }\n        if (show) { // hide in 3.3 seconds:\n            iv->when = rt_clock.seconds() + 3.3;\n        } else {\n            iv->when = 0;\n        }\n    }\n}\n\nstatic void ui_image_fit_fill_scale(ui_image_t* iv) {\n    fp64_t s = ui_image.scale(iv);\n    rt_assert(s != 0);\n    if (s > 1) {\n        ui_view.set_text(&iv->tool.ratio, \"1:%.3f\", s);\n    } else if (s != 0 && s <= 1) {\n        ui_view.set_text(&iv->tool.ratio, \"%.3f:1\", 1.0 / s);\n    } else {\n        // s should not be zero ever\n    }\n}\n\nstatic void ui_image_measure(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (!v->focusable) {\n        v->w = (int32_t)(iv->image.w * ui_image.scale(iv));\n        v->h = (int32_t)(iv->image.h * ui_image.scale(iv));\n        if (iv->fit || iv->fill) {\n            ui_image_fit_fill_scale(iv);\n        }\n    } else {\n        v->w = 0;\n        v->h = 0;\n    }\n}\n\nstatic void ui_image_layout(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (iv->fit || iv->fill) {\n        ui_image_fit_fill_scale(iv);\n        ui_view.measure_control(&iv->tool.ratio);\n    }\n    iv->tool.bar.x = v->x + v->w - iv->tool.bar.w;\n    iv->tool.bar.y = v->y;\n    iv->tool.ratio.x = v->x + v->w - iv->tool.ratio.w;\n    iv->tool.ratio.y = v->y + v->h - iv->tool.ratio.h;\n}\n\nstatic void ui_image_every_100ms(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (iv->when != 0 && rt_clock.seconds() > iv->when) {\n        ui_image_show_tools(iv, false);\n    }\n}\n\nstatic void ui_image_focus_lost(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    ui_image_show_tools(iv, ui_view.has_focus(v));\n}\n\nstatic void ui_image_focus_gained(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    ui_image_show_tools(iv, ui_view.has_focus(v));\n}\n\nstatic void ui_image_zoomed(ui_image_t* iv) {\n    iv->fill = false;\n    iv->fit  = false;\n    // 0=16:1 1=8:1 2=4:1 3=2:1 4=1:1 5=1:2 6=1:4 7=1:8 8=1:16\n    int32_t n  = iv->zoom - 4;\n    int32_t zn = iv->zn;\n    int32_t zd = iv->zd;\n    fp64_t scale_before = ui_image.scale(iv);\n    if (n > 0) {\n        zn = n + 1;\n        zd = 1;\n    } else if (n < 0) {\n        zn = 1;\n        zd = -n + 1;\n    } else if (n == 0) {\n        zn = 1;\n        zd = 1;\n    }\n    fp64_t scale_after = ui_image_scale_of(zn, zd);\n    if (scale_after != scale_before) {\n        iv->zn = zn;\n        iv->zd = zd;\n        const int32_t nm = 1 << (iv->zn - 1);\n        const int32_t dm = 1 << (iv->zd - 1);\n        ui_view.set_text(&iv->tool.ratio, \"%d:%d\", nm, dm);\n    }\n    if (iv->zn == 1) {\n        iv->zoom = 4 - (iv->zd - 1);\n    } else if (iv->zd == 1) {\n        iv->zoom = 4 + (iv->zn - 1);\n    } else {\n        rt_swear(false);\n    }\n    // is whole image visible?\n    fp64_t s = ui_image.scale(iv);\n    bool whole = (int32_t)(iv->image.w * s) <= iv->w &&\n                 (int32_t)(iv->image.h * s) <= iv->h;\n    if (whole) { iv->sx = 0.5; iv->sy = 0.5; }\n    ui_view.invalidate(&iv->view, null);\n    ui_image_show_tools(iv, true);\n}\n\nstatic void ui_image_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    fp64_t dx = (fp64_t)dx_dy.x;\n    fp64_t dy = (fp64_t)dx_dy.y;\n    ui_image_t* iv = (ui_image_t*)v;\n    if (ui_view.has_focus(v)) {\n        fp64_t s = ui_image.scale(iv);\n        if (iv->image.w * s > iv->w || iv->image.h * s > iv->h) {\n            iv->sx = max(0.0, min(iv->sx + dx / iv->image.w, 1.0));\n        } else {\n            iv->sx = 0.5;\n        }\n        if (iv->image.h * s > iv->h) {\n            iv->sy = max(0.0, min(iv->sy + dy / iv->image.h, 1.0));\n        } else {\n            iv->sy = 0.5;\n        }\n        ui_view.invalidate(&iv->view, null);\n    }\n}\n\nstatic bool ui_image_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false;\n    if (v->focusable) {\n        ui_image_t* iv = (ui_image_t*)v;\n        const int32_t x = ui_app.mouse.x - iv->x;\n        const int32_t y = ui_app.mouse.y - iv->y;\n        bool tools  = !iv->tool.bar.state.hidden &&\n                      ui_view.inside(&iv->tool.bar, &ui_app.mouse);\n        bool inside = ui_view.inside(&iv->view, &ui_app.mouse) && !tools;\n        bool left   = ix == 0;\n        bool drag_started = iv->drag_start.x >= 0 && iv->drag_start.y >= 0;\n        if (left && inside && !drag_started) {\n            iv->drag_start = (ui_point_t){x, y};\n        }\n        if (!pressed) {\n            iv->drag_start = (ui_point_t){-1, -1};\n        }\n        swallow = inside || tools;\n    }\n//  rt_println(\"inside %s\", inside ? \"true\" : \"false\");\n    return swallow;\n}\n\nstatic bool ui_image_mouse_move(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    bool drag_started = iv->drag_start.x >= 0 && iv->drag_start.y >= 0;\n    bool tools  = !iv->tool.bar.state.hidden &&\n                  ui_view.inside(&iv->tool.bar, &ui_app.mouse);\n    bool inside = ui_view.inside(&iv->view, &ui_app.mouse) && !tools;\n    if (drag_started && inside) {\n        ui_image_show_tools(iv, false);\n        const int32_t x = ui_app.mouse.x - iv->x;\n        const int32_t y = ui_app.mouse.y - iv->y;\n        ui_point_t dx_dy = {iv->drag_start.x - x, iv->drag_start.y - y};\n        ui_image_mouse_scroll(v, dx_dy);\n        iv->drag_start = (ui_point_t){x, y};\n    } else if (inside) {\n        ui_image_show_tools(iv, true);\n    } else if (!inside && !tools) {\n        ui_image_show_tools(iv, false);\n    }\n//  rt_println(\"inside %s\", inside ? \"true\" : \"false\");\n    return inside;\n}\n\nstatic bool ui_image_key_pressed(ui_view_t* v, int64_t vk) {\n    ui_image_t* iv = (ui_image_t*)v;\n    bool swallowed = false;\n    if (ui_view.has_focus(v)) {\n        swallowed = true;\n        if (vk == ui.key.up) {\n            ui_image_mouse_scroll(v, (ui_point_t){0, -iv->h / 8});\n        } else if (vk == ui.key.down) {\n            ui_image_mouse_scroll(v, (ui_point_t){0, +iv->h / 8});\n        } else if (vk == ui.key.left) {\n            ui_image_mouse_scroll(v, (ui_point_t){-iv->w / 8, 0});\n        } else if (vk == ui.key.right) {\n            ui_image_mouse_scroll(v, (ui_point_t){+iv->w / 8, 0});\n        } else if (vk == ui.key.plus) {\n            if (iv->zoom < 8) {\n                iv->zoom++;\n                ui_image_zoomed(iv);\n            }\n        } else if (vk == ui.key.minus) {\n            if (iv->zoom > 0) {\n                iv->zoom--;\n                ui_image_zoomed(iv);\n            }\n        } else {\n            swallowed = false;\n        }\n    }\n    return swallowed;\n}\n\nstatic void ui_image_zoom_in(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    if (iv->zoom < 8) {\n        iv->zoom++;\n        ui_image_zoomed(iv);\n    }\n}\n\nstatic void ui_image_zoom_out(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    if (iv->zoom > 0) {\n        iv->zoom--;\n        ui_image_zoomed(iv);\n    }\n}\n\nstatic void ui_image_fit(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->fit  = true;\n    iv->fill = false;\n    ui_image_fit_fill_scale(iv);\n    ui_view.invalidate(&iv->view, null);\n}\n\nstatic void ui_image_fill(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->fill = true;\n    iv->fit  = false;\n    ui_image_fit_fill_scale(iv);\n    ui_view.invalidate(&iv->view, null);\n}\n\nstatic void ui_image_zoom_1t1(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->zoom = 4;\n    ui_image_zoomed(iv);\n}\n\nstatic ui_label_t ui_image_about = ui_label(0,\n    \"Keyboard shortcuts:\\n\\n\"\n    \"Ctrl+C copies image to the clipboard.\\n\\n\"\n    rt_glyph_heavy_plus_sign \" zoom in; \"\n    rt_glyph_heavy_minus_sign \" zoom out;\\n\"\n    rt_glyph_open_circle_arrows_one_overlay \" 1:1.\\n\\n\"\n    rt_glyph_up_down_arrow \" Fit;\\n\"\n    rt_glyph_left_right_arrow \" Fill.\\n\\n\"\n    \"Left/Right Arrows \"\n    rt_glyph_leftward_arrow\n    rt_glyph_rightwards_arrow\n    \"Up/Down Arrows \"\n    rt_glyph_upwards_arrow\n    rt_glyph_downwards_arrow\n    \"\\npans the image inside view.\\n\\n\"\n    \"Mouse wheel or mouse / touchpad hold and drag to pan.\\n\"\n);\n\nstatic void ui_image_help(ui_button_t* rt_unused(b)) {\n    ui_app.show_toast(&ui_image_about, 7.0);\n}\n\nstatic void ui_image_copy_to_clipboard(ui_image_t* iv) {\n    ui_bitmap_t image = {0};\n    if (iv->image.texture != null) {\n        rt_clipboard.put_image(&iv->image);\n    } else {\n        ui_gdi.bitmap_init(&image, iv->image.w, iv->image.h,\n                                  iv->image.bpp, iv->image.pixels);\n        rt_clipboard.put_image(&image);\n        ui_gdi.bitmap_dispose(&image);\n    }\n    static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n    ui_app.show_hint(&hint, ui_app.mouse.x,\n                            ui_app.mouse.y + iv->fm->height,\n                     1.5);\n}\n\nstatic void ui_image_copy(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    ui_image_copy_to_clipboard(iv);\n}\n\nstatic void ui_image_character(ui_view_t* v, const char* utf8) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (ui_view.has_focus(v)) { // && ui_app.ctrl ?\n        char ch = utf8[0];\n        if (ch == '+' || ch == '=') {\n            if (iv->zoom < 8) {\n                iv->zoom++;\n                ui_image_zoomed(iv);\n            }\n        } else if (ch == '-' || ch == '_') {\n            if (iv->zoom > 0) {\n                iv->zoom--;\n                ui_image_zoomed(iv);\n            }\n        } else if (ch == '<' || ch == ',') {\n            ui_image_mouse_scroll(v, (ui_point_t){-iv->w / 8, 0});\n        } else if (ch == '>' || ch == '.') {\n            ui_image_mouse_scroll(v, (ui_point_t){+iv->w / 8, 0});\n        } else if (ch == '0') {\n            iv->zoom = 4;\n            ui_image_zoomed(iv);\n        } else if (ch == 3 && iv->image.pixels != null) { // Ctrl+C\n            ui_image_copy_to_clipboard(iv);\n        }\n    }\n}\n\nstatic void ui_image_add_button(ui_image_t* iv, ui_button_t* b,\n    const char* label, void (*cb)(ui_button_t* b), const char* hint) {\n    *b = (ui_button_t)ui_button(\"\", 0.0f, cb);\n    ui_view.set_text(b, label);\n    b->that = iv;\n    b->insets.top = 0;\n    b->insets.bottom = 0;\n    b->padding.top = 0;\n    b->padding.bottom = 0;\n    b->insets  = (ui_margins_t){0};\n    b->padding = (ui_margins_t){0};\n    b->flat = true;\n    b->fm = &ui_app.fm.mono.normal;\n    b->min_w_em = 1.5f;\n    rt_str_printf(b->hint, \"%s\", hint);\n    ui_view.add_last(&iv->tool.bar, b);\n}\n\nvoid ui_image_init(ui_image_t* iv) {\n    memset(iv, 0x00, sizeof(*iv));\n    iv->type         = ui_view_image;\n    iv->paint        = ui_image_paint;\n    iv->tap          = ui_image_tap;\n    iv->mouse_move   = ui_image_mouse_move;\n    iv->measure      = ui_image_measure;\n    iv->layout       = ui_image_layout;\n    iv->every_100ms  = ui_image_every_100ms;\n    iv->focus_lost   = ui_image_focus_lost;\n    iv->focus_gained = ui_image_focus_gained;\n    iv->mouse_scroll = ui_image_mouse_scroll;\n    iv->character    = ui_image_character;\n    iv->key_pressed  = ui_image_key_pressed;\n    iv->fm           = &ui_app.fm.prop.normal;\n    iv->tool.bar = (ui_view_t)ui_view(span);\n    // buttons:\n    ui_image_add_button(iv, &iv->tool.copy, \"\\xF0\\x9F\\x93\\x8B\", ui_image_copy,\n        \"Copy to Clipboard Ctrl+C\");\n    ui_image_add_button(iv, &iv->tool.zoom_out,\n                    rt_glyph_heavy_minus_sign,\n                    ui_image_zoom_out, \"Zoom Out\");\n    ui_image_add_button(iv, &iv->tool.zoom_1t1,\n                    rt_glyph_open_circle_arrows_one_overlay,\n                    ui_image_zoom_1t1, \"Reset to 1:1\");\n    ui_image_add_button(iv, &iv->tool.zoom_in,\n                     rt_glyph_heavy_plus_sign,\n                     ui_image_zoom_in,  \"Zoom In\");\n    ui_image_add_button(iv, &iv->tool.fit,\n                     rt_glyph_up_down_arrow,\n                     ui_image_fit,  \"Fit\");\n    ui_image_add_button(iv, &iv->tool.fill,\n                     rt_glyph_left_right_arrow,\n                     ui_image_fill,  \"Fill\");\n    ui_image_add_button(iv, &iv->tool.help,\n                     \"?\", ui_image_help, \"Help\");\n    iv->tool.zoom_1t1.min_w_em = 1.25f;\n    iv->tool.ratio = (ui_label_t)ui_label(0, \"1:1\");\n    iv->tool.ratio.color = ui_colors.get_color(ui_color_id_highlight);\n    iv->tool.ratio.color_id = ui_color_id_highlight;\n    ui_view.add_last(&iv->view, &iv->tool.bar);\n    ui_view.add_last(&iv->view, &iv->tool.ratio);\n    iv->tool.bar.state.hidden = true;\n    iv->tool.ratio.state.hidden = true;\n    iv->tool.bar.erase   = ui_image_tools_background;\n    iv->tool.ratio.erase = ui_image_tools_background;\n    iv->zoom = 4;\n    iv->zn = 1;\n    iv->zd = 1;\n    iv->sx = 0.5;\n    iv->sy = 0.5;\n    iv->drag_start = (ui_point_t){-1, -1};\n    iv->debug.id = \"#image\";\n}\n\nvoid ui_image_init_with(ui_image_t* iv, const uint8_t* pixels,\n                                  int32_t w, int32_t h,\n                                  int32_t c, int32_t s) {\n    ui_image_init(iv);\n    iv->image.pixels = (uint8_t*)pixels;\n    iv->image.w = w;\n    iv->image.h = h;\n    iv->image.bpp = c;\n    iv->image.stride = s;\n}\n\nstatic void ui_image_ratio(ui_image_t* iv, int32_t zn, int32_t zd) {\n    rt_swear(0 < zn && zn <= 16);\n    rt_swear(0 < zd && zd <= 16);\n    // only 1:2 and 2:1 etc are supported:\n    if (zn != 1) { rt_swear(zd == 1); }\n    if (zd != 1) { rt_swear(zn == 1); }\n    iv->zn = zn;\n    iv->zd = zd;\n    iv->fit  = false;\n    iv->fill = false;\n}\n\nui_image_if ui_image = {\n    .init      = ui_image_init,\n    .init_with = ui_image_init_with,\n    .ratio     = ui_image_ratio,\n    .scale     = ui_image_scale,\n    .position  = ui_image_position\n};\n\n// ________________________________ ui_label.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic void ui_label_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_label);\n    rt_assert(!ui_view.is_hidden(v));\n    const char* s = ui_view.string(v);\n    ui_color_t c = v->state.hover && v->highlightable ?\n        ui_colors.interpolate(v->color, ui_colors.blue, 1.0f / 8.0f) :\n        v->color;\n    const int32_t tx = v->x + v->text.xy.x;\n    const int32_t ty = v->y + v->text.xy.y;\n    const ui_gdi_ta_t ta = { .fm = v->fm, .color = c };\n    const bool multiline = strchr(s, '\\n') != null;\n    if (multiline) {\n        int32_t w = (int32_t)((fp64_t)v->min_w_em * (fp64_t)v->fm->em.w + 0.5);\n        ui_gdi.multiline(&ta, tx, ty, w, \"%s\", ui_view.string(v));\n    } else {\n        ui_gdi.text(&ta, tx, ty, \"%s\", ui_view.string(v));\n    }\n    if (v->state.hover && !v->flat && v->highlightable) {\n        ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n        int32_t radius = (v->fm->em.h / 4) | 0x1; // corner radius\n        int32_t h = multiline ? v->h : v->fm->baseline + v->fm->descent;\n        ui_gdi.rounded(v->x - radius, v->y, v->w + 2 * radius, h,\n                       radius, highlight, ui_colors.transparent);\n    }\n}\n\nstatic bool ui_label_context_menu(ui_view_t* v) {\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        rt_clipboard.put_text(ui_view.string(v));\n        static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n        int32_t x = v->x + v->w / 2;\n        int32_t y = v->y + v->h;\n        ui_app.show_hint(&hint, x, y, 0.75);\n    }\n    return inside;\n}\n\nstatic void ui_label_character(ui_view_t* v, const char* utf8) {\n    rt_assert(v->type == ui_view_label);\n    if (v->state.hover && !ui_view.is_hidden(v)) {\n        char ch = utf8[0];\n        // Copy to clipboard works for hover over text\n        if ((ch == 3 || ch == 'c' || ch == 'C') && ui_app.ctrl) {\n            rt_clipboard.put_text(ui_view.string(v)); // 3 is ASCII for Ctrl+C\n        }\n    }\n}\n\nvoid ui_view_init_label(ui_view_t* v) {\n    rt_assert(v->type == ui_view_label);\n    v->paint         = ui_label_paint;\n    v->character     = ui_label_character;\n    v->context_menu  = ui_label_context_menu;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    v->text_align    = ui.align.left;\n}\n\nvoid ui_label_init_va(ui_label_t* v, fp32_t min_w_em,\n        const char* format, va_list va) {\n    ui_view.set_text(v, format, va);\n    v->min_w_em = min_w_em;\n    v->type = ui_view_label;\n    ui_view_init_label(v);\n}\n\nvoid ui_label_init(ui_label_t* v, fp32_t min_w_em, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_label_init_va(v, min_w_em, format, va);\n    va_end(va);\n}\n// _________________________________ ui_mbx.c _________________________________\n\n#include \"rt/rt.h\"\n\nstatic void ui_mbx_button(ui_button_t* b) {\n    ui_mbx_t* m = (ui_mbx_t*)b->parent;\n    rt_assert(m->type == ui_view_mbx);\n    m->option = -1;\n    for (int32_t i = 0; i < rt_countof(m->button) && m->option < 0; i++) {\n        if (b == &m->button[i]) {\n            m->option = i;\n            if (m->callback != null) {\n                m->callback(&m->view);\n                // need to disarm button because message box about to close\n                b->state.pressed = false;\n                b->state.armed = false;\n            }\n        }\n    }\n    ui_app.show_toast(null, 0);\n}\n\nstatic void ui_mbx_measured(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    int32_t n = 0;\n    ui_view_for_each(v, c, { n++; });\n    n--; // number of buttons\n    const int32_t em_x = m->label.fm->em.w;\n    const int32_t em_y = m->label.fm->em.h;\n    const int32_t tw = m->label.w;\n    const int32_t th = m->label.h;\n    if (n > 0) {\n        int32_t bw = 0;\n        for (int32_t i = 0; i < n; i++) {\n            bw += m->button[i].w;\n        }\n        v->w = rt_max(tw, bw + em_x * 2);\n        v->h = th + m->button[0].h + em_y + em_y / 2;\n    } else {\n        v->h = th + em_y / 2;\n        v->w = tw;\n    }\n}\n\nstatic void ui_mbx_layout(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    int32_t n = 0;\n    ui_view_for_each(v, c, { n++; });\n    n--; // number of buttons\n    const int32_t em_y = m->label.fm->em.h;\n    m->label.x = v->x;\n    m->label.y = v->y + em_y * 2 / 3;\n    const int32_t tw = m->label.w;\n    const int32_t th = m->label.h;\n    if (n > 0) {\n        int32_t bw = 0;\n        for (int32_t i = 0; i < n; i++) {\n            bw += m->button[i].w;\n        }\n        // center text:\n        m->label.x = v->x + (v->w - tw) / 2;\n        // spacing between buttons:\n        int32_t sp = (v->w - bw) / (n + 1);\n        int32_t x = sp;\n        for (int32_t i = 0; i < n; i++) {\n            m->button[i].x = v->x + x;\n            m->button[i].y = v->y + th + em_y * 3 / 2;\n            x += m->button[i].w + sp;\n        }\n    }\n}\n\nvoid ui_view_init_mbx(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    v->measured = ui_mbx_measured;\n    v->layout = ui_mbx_layout;\n    m->fm = &ui_app.fm.prop.normal;\n    int32_t n = 0;\n    while (m->options[n] != null && n < rt_countof(m->button) - 1) {\n        m->button[n] = (ui_button_t)ui_button(\"\", 6.0, ui_mbx_button);\n        ui_view.set_text(&m->button[n], \"%s\", m->options[n]);\n        n++;\n    }\n    rt_swear(n <= rt_countof(m->button), \"inhumane: %d buttons is too many\", n);\n    if (n > rt_countof(m->button)) { n = rt_countof(m->button); }\n    m->label = (ui_label_t)ui_label(0, \"\");\n    ui_view.set_text(&m->label, \"%s\", ui_view.string(&m->view));\n    ui_view.add_last(&m->view, &m->label);\n    for (int32_t i = 0; i < n; i++) {\n        ui_view.add_last(&m->view, &m->button[i]);\n        m->button[i].fm = m->fm;\n    }\n    m->label.fm = m->fm;\n    ui_view.set_text(&m->view, \"\");\n    m->option = -1;\n    if (m->debug.id == null) { m->debug.id = \"#mbx\"; }\n}\n\nvoid ui_mbx_init(ui_mbx_t* m, const char* options[],\n        const char* format, ...) {\n    m->type = ui_view_mbx;\n    m->measured  = ui_mbx_measured;\n    m->layout    = ui_mbx_layout;\n    m->color_id  = ui_color_id_window;\n    m->options   = options;\n    m->focusable = true;\n    va_list va;\n    va_start(va, format);\n    ui_view.set_text_va(&m->view, format, va);\n    ui_label_init(&m->label, 0.0, ui_view.string(&m->view));\n    va_end(va);\n    ui_view_init_mbx(&m->view);\n}\n// ________________________________ ui_midi.c _________________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n#include <mmsystem.h>\n\n#pragma comment(lib, \"winmm\")\n\ntypedef struct ui_midi_s_ {\n    MCI_OPEN_PARMSA mop; // opaque\n    ui_app_message_handler_t handler;\n    char alias[32];\n    int64_t device_id;\n    uintptr_t window;\n    bool playing;\n} ui_midi_t_;\n\nrt_static_assertion(sizeof(ui_midi_t) >= sizeof(ui_midi_t_) + sizeof(void*));\nrt_static_assertion(MMSYSERR_NOERROR == 0);\n\nstatic void ui_midi_error(errno_t r, char* text, int32_t count) {\n    rt_fatal_win32err(mciGetErrorStringA(r, text, (UINT)count));\n}\n\nstatic void ui_midi_warn_if_error_(int r, const char* call, const char* func,\n        int line) {\n    if (r != 0) {\n        static char error[256];\n        ui_midi_error(r, error, rt_countof(error));\n        rt_println(\"%s:%d %s\", func, line, call);\n        rt_println(\"%d - MCIERR_BASE: %d %s\", r, r - MCIERR_BASE, error);\n    }\n}\n\n#define ui_midi_warn_if_error(r) do {                  \\\n    ui_midi_warn_if_error_(r, #r, __func__, __LINE__); \\\n} while (0)\n\n#define ui_midi_fatal_if_error(call) do {                                   \\\n    int _r_ = call; ui_midi_warn_if_error_(r, #call, __func__, __LINE__);   \\\n    rt_fatal_if_error(r);                                                   \\\n} while (0)\n\nstatic bool ui_midi_message_callback(ui_app_message_handler_t* h, int32_t m,\n                                     int64_t wp, int64_t lp, int64_t* rt) {\n    if (m == MM_MCINOTIFY) {\n        #ifdef UI_MIDI_DEBUG\n            rt_println(\"device_id: %lld\", lp);\n            if (wp & MCI_NOTIFY_SUCCESSFUL) { rt_println(\"SUCCESSFUL\"); }\n            if (wp & MCI_NOTIFY_SUPERSEDED) { rt_println(\"SUPERSEDED\"); }\n            if (wp & MCI_NOTIFY_ABORTED)    { rt_println(\"ABORTED\");    }\n            if (wp & MCI_NOTIFY_FAILURE)    { rt_println(\"FAILURE\");    }\n        #endif\n        ui_midi_t* midi = (ui_midi_t*)h->that;\n        ui_midi_t_* mi  = (ui_midi_t_*)midi;\n        if (mi->device_id == lp) {\n            if (midi->notify != null) {\n                *rt = midi->notify(midi, wp);\n            } else {\n                *rt = 0;\n            }\n            return true;\n        }\n    }\n    return false;\n}\n\nstatic void ui_midi_remove_handler(ui_midi_t* m) {\n    ui_midi_t_* mi  = (ui_midi_t_*)m;\n    ui_app_message_handler_t* h = ui_app.handlers;\n    if (h == &mi->handler) {\n        ui_app.handlers = h->next;\n    } else {\n        while (h->next != null && h->next != &mi->handler) {\n            h = h->next;\n        }\n        rt_swear(h->next == &mi->handler);\n        if (h->next == &mi->handler) {\n            h->next = h->next->next;\n        }\n    }\n    mi->handler.callback = null;\n    mi->handler.that = null;\n    mi->handler.next = null;\n}\n\nstatic errno_t ui_midi_open(ui_midi_t* m, const char* filename) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    mi->handler.that = mi;\n    mi->handler.next = ui_app.handlers;\n    ui_app.handlers = &mi->handler;\n    mi->window = (uintptr_t)ui_app.window;\n    mi->playing = false;\n    mi->mop.dwCallback = mi->window;\n    mi->mop.wDeviceID = (WORD)-1;\n    mi->mop.lpstrDeviceType = (const char*)MCI_DEVTYPE_SEQUENCER;\n    mi->mop.lpstrElementName = filename;\n    mi->mop.lpstrAlias = mi->alias;\n    rt_str_printf(mi->alias, \"%p\", m);\n    const DWORD_PTR flags = MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |\n                            MCI_OPEN_ELEMENT | MCI_OPEN_ALIAS;\n    errno_t r = mciSendCommandA(0, MCI_OPEN, flags, (uintptr_t)&mi->mop);\n    ui_midi_warn_if_error(r);\n    rt_assert(mi->mop.wDeviceID != -1);\n    mi->handler.callback = ui_midi_message_callback,\n    mi->device_id = mi->mop.wDeviceID;\n    if (r != 0) {\n        ui_midi_remove_handler(m);\n        memset(&mi->mop, 0x00, sizeof(mi->mop));\n        mi->window = 0;\n    }\n    return r;\n}\n\nstatic errno_t ui_midi_play(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    rt_swear(ui_midi.is_open(m));\n    MCI_PLAY_PARMS  pp = { .dwCallback = (uintptr_t)mi->window };\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_PLAY, MCI_NOTIFY, (uintptr_t)&pp);\n    ui_midi_warn_if_error(r);\n    if (r == 0) {\n        mi->playing = true;\n    }\n    return r;\n}\n\nstatic errno_t ui_midi_rewind(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    MCI_SEEK_PARMS p = { .dwCallback = (uintptr_t)mi->window, .dwTo = 0 };\n    const DWORD f = MCI_WAIT|MCI_SEEK_TO_START;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_SEEK, f, (DWORD_PTR)&p);\n    ui_midi_warn_if_error(r);\n    return r;\n}\n\nstatic errno_t ui_midi_get_volume(ui_midi_t* m, fp64_t* volume) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    DWORD v = 0;\n    errno_t r = midiOutGetVolume((HMIDIOUT)0, &v);\n    ui_midi_warn_if_error(r);\n    *volume = (fp64_t)v / (fp64_t)0xFFFFFFFFU;\n    return 0;\n}\n\nstatic errno_t ui_midi_set_volume(ui_midi_t* m, fp64_t volume) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    DWORD v = (DWORD)(volume * (fp64_t)0xFFFFFFFFU);\n    const UINT n = midiOutGetNumDevs();\n    // Handle to a MIDI Output Device\n    HMIDIOUT h = (HMIDIOUT)(uintptr_t)(n - 1);\n    errno_t r = n == 0 ? MCIERR_DEVICE_NOT_INSTALLED : midiOutSetVolume(h, v);\n    ui_midi_warn_if_error(r);\n    rt_fatal_if_error(r);\n    return r;\n}\n\nstatic errno_t ui_midi_stop(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_STOP, 0, 0);\n    ui_midi_warn_if_error(r);\n    if (r == 0) { mi->playing = false; }\n    return r;\n}\n\nstatic void ui_midi_close(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && !ui_midi.is_playing(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_CLOSE, MCI_WAIT, 0);\n    ui_midi_warn_if_error(r);\n    r = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0);\n    ui_midi_warn_if_error(r);\n    rt_fatal_if_error(r, \"sound card is unplugged on the fly?\");\n    memset(&mi->mop, 0x00, sizeof(mi->mop));\n    mi->window = 0;\n    ui_midi_remove_handler(m);\n}\n\nstatic bool ui_midi_is_open(ui_midi_t* m) {\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    return mi->window != 0;\n}\n\nstatic bool ui_midi_is_playing(ui_midi_t* m) {\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    return mi->playing;\n}\n\nui_midi_if ui_midi = {\n    .success    = MCI_NOTIFY_SUCCESSFUL,\n    .failure    = MCI_NOTIFY_FAILURE,\n    .aborted    = MCI_NOTIFY_ABORTED,\n    .superseded = MCI_NOTIFY_SUPERSEDED,\n    .error      = ui_midi_error,\n    .open       = ui_midi_open,\n    .play       = ui_midi_play,\n    .rewind     = ui_midi_rewind,\n    .get_volume = ui_midi_get_volume,\n    .set_volume = ui_midi_set_volume,\n    .stop       = ui_midi_stop,\n    .is_open    = ui_midi_is_open,\n    .is_playing = ui_midi_is_playing,\n    .close      = ui_midi_close\n};\n// _______________________________ ui_slider.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic void ui_slider_invalidate(const ui_slider_t* s) {\n    const ui_view_t* v = &s->view;\n    ui_view.invalidate(v, null);\n    if (!s->dec.state.hidden) { ui_view.invalidate(&s->dec, null); }\n    if (!s->inc.state.hidden) { ui_view.invalidate(&s->dec, null); }\n}\n\nstatic int32_t ui_slider_width(const ui_slider_t* s) {\n    const ui_ltrb_t i = ui_view.margins(&s->view, &s->insets);\n    int32_t w = s->w - i.left - i.right;\n    if (!s->dec.state.hidden) {\n        const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n        const ui_ltrb_t inc_p = ui_view.margins(&s->inc, &s->inc.padding);\n        w -= s->dec.w + s->inc.w + dec_p.right + inc_p.left;\n    }\n    return w;\n}\n\nstatic ui_wh_t measure_text(const ui_fm_t* fm, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    const ui_gdi_ta_t ta = { .fm = fm, .color = ui_colors.white, .measure = true };\n    ui_wh_t wh = ui_gdi.text_va(&ta, 0, 0, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_slider_measure_text(ui_slider_t* s) {\n    char formatted[rt_countof(s->p.text)];\n    const ui_fm_t* fm = s->fm;\n    const char* text = ui_view.string(&s->view);\n    const ui_ltrb_t i = ui_view.margins(&s->view, &s->insets);\n    ui_wh_t wh = s->fm->em;\n    if (s->debug.trace.mt) {\n        const ui_ltrb_t p = ui_view.margins(&s->view, &s->padding);\n        rt_println(\">%dx%d em: %dx%d min: %.1fx%.1f \"\n                \"i: %d %d %d %d p: %d %d %d %d \\\"%.*s\\\"\",\n            s->w, s->h, fm->em.w, fm->em.h, s->min_w_em, s->min_h_em,\n            i.left, i.top, i.right, i.bottom,\n            p.left, p.top, p.right, p.bottom,\n            rt_min(64, strlen(text)), text);\n        const ui_margins_t in = s->insets;\n        const ui_margins_t pd = s->padding;\n        rt_println(\" i: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\"\n                \" p: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\",\n            in.left, in.top, in.right, in.bottom,\n            in.left + in.right, in.top + in.bottom,\n            pd.left, pd.top, pd.right, pd.bottom,\n            pd.left + pd.right, pd.top + pd.bottom);\n    }\n    if (s->format != null) {\n        s->format(&s->view);\n        rt_str_printf(formatted, \"%s\", text);\n        wh = measure_text(s->fm, \"%s\", formatted);\n        // TODO: format string 0x08X?\n    } else if (text != null && (strstr(text, \"%d\") != null ||\n                                strstr(text, \"%u\") != null)) {\n        ui_wh_t mt_min = measure_text(s->fm, text, s->value_min);\n        ui_wh_t mt_max = measure_text(s->fm, text, s->value_max);\n        ui_wh_t mt_val = measure_text(s->fm, text, s->value);\n        wh.h = rt_max(mt_val.h, rt_max(mt_min.h, mt_max.h));\n        wh.w = rt_max(mt_val.w, rt_max(mt_min.w, mt_max.w));\n    } else if (text != null && text[0] != 0) {\n        wh = measure_text(s->fm, \"%s\", text);\n    }\n    if (s->debug.trace.mt) {\n        rt_println(\" mt: %dx%d\", wh.w, wh.h);\n    }\n    return wh;\n}\n\nstatic void ui_slider_measure(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // slider cannot be smaller than 2*em\n    const fp32_t min_w_em = rt_max(2.0f, v->min_w_em);\n    v->w = (int32_t)((fp64_t)fm->em.w * (fp64_t)   min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    // dec and inc have same font metrics as a slider:\n    s->dec.fm = fm;\n    s->inc.fm = fm;\n    rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"not the same\");\n    ui_view.measure_control(v);\n//  s->text.mt = ui_slider_measure_text(s);\n    if (s->dec.state.hidden) {\n        v->w = rt_max(v->w, i.left + s->wh.w + i.right);\n    } else {\n        ui_view.measure(&s->dec); // remeasure with inherited metrics\n        ui_view.measure(&s->inc);\n        const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n        const ui_ltrb_t inc_p = ui_view.margins(&s->inc, &s->inc.padding);\n        v->w = rt_max(v->w, s->dec.w + dec_p.right + s->wh.w + inc_p.left + s->inc.w);\n    }\n    v->h = rt_max(v->h, i.top + fm->em.h + i.bottom);\n    if (s->debug.trace.mt) {\n        rt_println(\"<%dx%d\", s->w, s->h);\n    }\n}\n\nstatic void ui_slider_layout(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    // disregard inc/dec .state.hidden bit for layout:\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    s->dec.x = v->x + i.left;\n    s->dec.y = v->y;\n    s->inc.x = v->x + v->w - i.right - s->inc.w;\n    s->inc.y = v->y;\n}\n\nstatic void ui_slider_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n    // dec button is sticking to the left into slider padding\n    const int32_t dec_w = s->dec.w + dec_p.right;\n    rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"hidden or not together\");\n    const int32_t dx = s->dec.state.hidden ? 0 : dec_w;\n    const int32_t x = v->x + dx + i.left;\n    const int32_t w = ui_slider_width(s);\n    // draw background:\n    fp32_t d = ui_theme.is_app_dark() ? 0.50f : 0.25f;\n    ui_color_t d0 = ui_colors.darken(v->background, d);\n    d /= 4;\n    ui_color_t d1 = ui_colors.darken(v->background, d);\n    ui_gdi.gradient(x, v->y, w, v->h, d1, d0, true);\n    // draw value:\n    ui_color_t c = ui_theme.is_app_dark() ?\n        ui_colors.darken(ui_colors.green, 1.0f / 128.0f) :\n        ui_colors.jungle_green;\n    d1 = c;\n    d0 = ui_colors.darken(c, 1.0f / 64.0f);\n    const fp64_t range = (fp64_t)s->value_max - (fp64_t)s->value_min;\n    rt_assert(range > 0, \"range: %.6f\", range);\n    const fp64_t  vw = (fp64_t)w * (s->value - s->value_min) / range;\n    const int32_t wi = (int32_t)(vw + 0.5);\n    ui_gdi.gradient(x, v->y, wi, v->h, d1, d0, true);\n    if (!v->flat) {\n        ui_color_t color = v->state.hover ?\n            ui_colors.get_color(ui_color_id_hot_tracking) :\n            ui_colors.get_color(ui_color_id_gray_text);\n        if (ui_view.is_disabled(v)) { color = ui_color_rgb(30, 30, 30); } // TODO: hardcoded\n        ui_gdi.frame(x, v->y, w, v->h, color);\n    }\n    // text:\n    const char* text = ui_view.string(v);\n    char formatted[rt_countof(v->p.text)];\n    if (s->format != null) {\n        s->format(v);\n        s->p.strid = 0; // nls again\n        text = ui_view.string(v);\n    } else if (text != null &&\n        (strstr(text, \"%d\") != null || strstr(text, \"%u\") != null)) {\n        rt_str.format(formatted, rt_countof(formatted), text, s->value);\n        s->p.strid = 0; // nls again\n        text = rt_nls.str(formatted);\n    }\n    // because current value was formatted into `text` need to\n    // remeasure and align text again:\n    ui_view.text_measure(v, text, &v->text);\n    ui_view.text_align(v, &v->text);\n    const ui_color_t text_color = !v->state.hover ? v->color :\n            (ui_theme.is_app_dark() ? ui_colors.white : ui_colors.black);\n    const ui_gdi_ta_t ta = { .fm = fm, .color = text_color };\n    ui_gdi.text(&ta, v->x + v->text.xy.x, v->y + v->text.xy.y, \"%s\", text);\n}\n\nstatic bool ui_slider_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        if (pressed) {\n            ui_slider_t* s = (ui_slider_t*)v;\n            const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n            const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n            const int32_t dec_w = s->dec.w + dec_p.right;\n            rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"hidden or not together\");\n            const int32_t sw = ui_slider_width(s); // slider width\n            const int32_t dx = s->dec.state.hidden ? 0 : dec_w + dec_p.right;\n            const int32_t vx = v->x + i.left + dx;\n            const int32_t x = ui_app.mouse.x - vx;\n            const int32_t y = ui_app.mouse.y - (v->y + i.top);\n            if (0 <= x && x < sw && 0 <= y && y < v->h) {\n                const fp64_t range = (fp64_t)s->value_max - (fp64_t)s->value_min;\n                fp64_t val = (fp64_t)x * range / (fp64_t)(sw - 1);\n                int32_t vw = (int32_t)(val + s->value_min + 0.5);\n                s->value = rt_min(rt_max(vw, s->value_min), s->value_max);\n                if (s->callback != null) { s->callback(&s->view); }\n                ui_slider_invalidate(s);\n            }\n        }\n    }\n    return pressed && inside; // swallow inside clicks\n}\n\nstatic void ui_slider_mouse_move(ui_view_t* v) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        ui_slider_t* s = (ui_slider_t*)v;\n        bool drag = ui_app.mouse_left || ui_app.mouse_right;\n        if (drag) {\n            const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n            const int32_t dec_w = s->dec.w + dec_p.right;\n            rt_assert(s->dec.state.hidden == s->inc.state.hidden,\n                      \".dec .inc must be .hidden in sync\");\n            const int32_t sw = ui_slider_width(s); // slider width\n            const int32_t dx = s->dec.state.hidden ? 0 : dec_w + dec_p.right;\n            const int32_t vx = v->x + i.left + dx;\n            const int32_t x = ui_app.mouse.x - vx;\n            const int32_t y = ui_app.mouse.y - (v->y + i.top);\n            if (0 <= x && x < sw && 0 <= y && y < v->h) {\n                const fp64_t fmax = (fp64_t)s->value_max;\n                const fp64_t fmin = (fp64_t)s->value_min;\n                const fp64_t range = fmax - fmin;\n                fp64_t val = (fp64_t)x * range / (fp64_t)(sw - 1);\n                int32_t vw = (int32_t)(val + s->value_min + 0.5);\n                s->value = rt_min(rt_max(vw, s->value_min), s->value_max);\n                if (s->callback != null) { s->callback(&s->view); }\n                ui_slider_invalidate(s);\n            }\n        }\n    }\n}\n\nstatic void ui_slider_inc_dec_value(ui_slider_t* s, int32_t sign, int32_t mul) {\n    if (!ui_view.is_hidden(&s->view) && !ui_view.is_disabled(&s->view)) {\n        // full 0x80000000..0x7FFFFFFF (-2147483648..2147483647) range\n        int32_t v = s->value;\n        if (v > s->value_min && sign < 0) {\n            mul = rt_min(v - s->value_min, mul);\n            v += mul * sign;\n        } else if (v < s->value_max && sign > 0) {\n            mul = rt_min(s->value_max - v, mul);\n            v += mul * sign;\n        }\n        if (s->value != v) {\n            s->value = v;\n            if (s->callback != null) { s->callback(&s->view); }\n            ui_slider_invalidate(s);\n        }\n    }\n}\n\nstatic void ui_slider_inc_dec(ui_button_t* b) {\n    ui_slider_t* s = (ui_slider_t*)b->parent;\n    if (!ui_view.is_hidden(&s->view) && !ui_view.is_disabled(&s->view)) {\n        int32_t sign = b == &s->inc ? +1 : -1;\n        int32_t mul = ui_app.shift && ui_app.ctrl ? 1000 :\n            ui_app.shift ? 100 : ui_app.ctrl ? 10 : 1;\n        ui_slider_inc_dec_value(s, sign, mul);\n    }\n}\n\nstatic void ui_slider_every_100ms(ui_view_t* v) { // 100ms\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    if (ui_view.is_hidden(v) || ui_view.is_disabled(v)) {\n        s->time = 0;\n    } else if (!s->dec.state.armed && !s->inc.state.armed) {\n        s->time = 0;\n    } else {\n        if (s->time == 0) {\n            s->time = ui_app.now;\n        } else if (ui_app.now - s->time > 1.0) {\n            const int32_t sign = s->dec.state.armed ? -1 : +1;\n            const int32_t sec = (int32_t)(ui_app.now - s->time + 0.5);\n            int32_t initial = ui_app.shift && ui_app.ctrl ? 1000 :\n                ui_app.shift ? 100 : ui_app.ctrl ? 10 : 1;\n            int32_t mul = sec >= 1 ? initial << (sec - 1) : initial;\n            const int64_t range = (int64_t)s->value_max - (int64_t)s->value_min;\n            if (mul > range / 8) { mul = (int32_t)(range / 8); }\n            ui_slider_inc_dec_value(s, sign, rt_max(mul, 1));\n        }\n    }\n}\n\nvoid ui_view_init_slider(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    v->measure       = ui_slider_measure;\n    v->layout        = ui_slider_layout;\n    v->paint         = ui_slider_paint;\n    v->tap           = ui_slider_tap;\n    v->mouse_move    = ui_slider_mouse_move;\n    v->every_100ms   = ui_slider_every_100ms;\n    v->color_id      = ui_color_id_window_text;\n    v->background_id = ui_color_id_button_face;\n    ui_slider_t* s = (ui_slider_t*)v;\n    static const char* accel =\n        \" Hold key while clicking\\n\"\n        \" Ctrl: x 10 Shift: x 100 \\n\"\n        \" Ctrl+Shift: x 1000 \\n for step multiplier.\";\n    s->dec = (ui_button_t)ui_button(rt_glyph_fullwidth_hyphen_minus, 0, // rt_glyph_heavy_minus_sign\n                                    ui_slider_inc_dec);\n    s->dec.fm = v->fm;\n    rt_str_printf(s->dec.hint, \"%s\", accel);\n    s->inc = (ui_button_t)ui_button(rt_glyph_fullwidth_plus_sign, 0, // rt_glyph_heavy_plus_sign\n                                    ui_slider_inc_dec);\n    s->inc.fm = v->fm;\n    ui_view.add(&s->view, &s->dec, &s->inc, null);\n    // single glyph buttons less insets look better:\n    ui_view_for_each(&s->view, it, {\n        it->insets.left   = 0.125f;\n        it->insets.right  = 0.125f;\n    });\n    // inherit initial padding and insets from buttons.\n    // caller may change those later and it should be accounted to\n    // in measure() and layout()\n    v->insets  = s->dec.insets;\n    v->padding = s->dec.padding;\n    s->dec.padding.right = 0;\n    s->dec.padding.left  = 0;\n    s->inc.padding.left  = 0;\n    s->inc.padding.right = 0;\n    s->dec.flat = true;\n    s->inc.flat = true;\n    s->dec.min_h_em = 1.0f + ui_view_i_tb * 2;\n    s->dec.min_w_em = 1.0f + ui_view_i_tb * 2;\n    s->inc.min_h_em = 1.0f + ui_view_i_tb * 2;\n    s->inc.min_w_em = 1.0f + ui_view_i_tb * 2;\n    rt_str_printf(s->inc.hint, \"%s\", accel);\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    if (v->debug.id == null) { v->debug.id = \"#slider\"; }\n}\n\nvoid ui_slider_init(ui_slider_t* s, const char* label, fp32_t min_w_em,\n        int32_t value_min, int32_t value_max,\n        void (*callback)(ui_view_t* r)) {\n    static_assert(offsetof(ui_slider_t, view) == 0, \"offsetof(.view)\");\n    if (min_w_em < 6.0) { rt_println(\"6.0 em minimum\"); }\n    s->type = ui_view_slider;\n    ui_view.set_text(&s->view, \"%s\", label);\n    s->callback = callback;\n    s->min_w_em = rt_max(6.0f, min_w_em);\n    s->value_min = value_min;\n    s->value_max = value_max;\n    s->value = value_min;\n    ui_view_init_slider(&s->view);\n}\n// ________________________________ ui_theme.c ________________________________\n\n/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n// ________________________________ rt_win32.h ________________________________\n\n#ifdef WIN32\n\n#pragma warning(push)\n#pragma warning(disable: 4255) // no function prototype: '()' to '(void)'\n#pragma warning(disable: 4459) // declaration of '...' hides global declaration\n\n#pragma push_macro(\"UNICODE\")\n#define UNICODE // always because otherwise IME does not work\n\n// ut:\n#include <Windows.h>  // used by:\n#include <Psapi.h>    // both rt_loader.c and rt_processes.c\n#include <shellapi.h> // rt_processes.c\n#include <winternl.h> // rt_processes.c\n#include <initguid.h>     // for knownfolders\n#include <KnownFolders.h> // rt_files.c\n#include <AclAPI.h>       // rt_files.c\n#include <ShlObj_core.h>  // rt_files.c\n#include <Shlwapi.h>      // rt_files.c\n// ui:\n#include <commdlg.h>\n#include <dbghelp.h>\n#include <dwmapi.h>\n#include <imm.h>\n#include <ShellScalingApi.h>\n#include <tlhelp32.h>\n#include <VersionHelpers.h>\n#include <windowsx.h>\n#include <winnt.h>\n\n#pragma pop_macro(\"UNICODE\")\n\n#pragma warning(pop)\n\n#include <fcntl.h>\n\n#define rt_export __declspec(dllexport)\n\n// Win32 API BOOL -> errno_t translation\n\n#define rt_b2e(call) ((errno_t)(call ? 0 : GetLastError()))\n\nvoid rt_win32_close_handle(void* h);\n/* translate ix to error */\nerrno_t rt_wait_ix2e(uint32_t r);\n\n\n#endif // WIN32\n\nstatic int32_t ui_theme_dark = -1; // -1 unknown\n\nstatic errno_t ui_theme_reg_get_uint32(HKEY root, const char* path,\n        const char* key, DWORD *v) {\n    *v = 0;\n    DWORD type = REG_DWORD;\n    DWORD light_theme = 0;\n    DWORD bytes = sizeof(light_theme);\n    errno_t r = RegGetValueA(root, path, key, RRF_RT_DWORD, &type, v, &bytes);\n    if (r != 0) {\n        rt_println(\"RegGetValueA(%s\\\\%s) failed %s\", path, key, rt_strerr(r));\n    }\n    return r;\n}\n\n#pragma push_macro(\"ux_theme_reg_cv\")\n#pragma push_macro(\"ux_theme_reg_default_colors\")\n\n#define ux_theme_reg_cv \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\\"\n#define ux_theme_reg_default_colors ux_theme_reg_cv \"Themes\\\\DefaultColors\\\\\"\n\nstatic bool ui_theme_use_light_theme(const char* key) {\n    if ((!ui_app.dark_mode && !ui_app.light_mode) ||\n        ( ui_app.dark_mode &&  ui_app.light_mode)) {\n        const char* personalize  = ux_theme_reg_cv \"Themes\\\\Personalize\";\n        DWORD light_theme = 0;\n        ui_theme_reg_get_uint32(HKEY_CURRENT_USER, personalize, key, &light_theme);\n        return light_theme != 0;\n    } else if (ui_app.light_mode) {\n        return true;\n    } else {\n        rt_assert(ui_app.dark_mode);\n        return false;\n    }\n}\n\n#pragma pop_macro(\"ux_theme_reg_cv\")\n#pragma pop_macro(\"ux_theme_reg_default_colors\")\n\nstatic HMODULE ui_theme_uxtheme(void) {\n    static HMODULE uxtheme;\n    if (uxtheme == null) {\n        uxtheme = GetModuleHandleA(\"uxtheme.dll\");\n        if (uxtheme == null) {\n            uxtheme = LoadLibraryA(\"uxtheme.dll\");\n        }\n    }\n    rt_not_null(uxtheme);\n    return uxtheme;\n}\n\nstatic void* ui_theme_uxtheme_func(uint16_t ordinal) {\n    HMODULE uxtheme = ui_theme_uxtheme();\n    void* proc = (void*)GetProcAddress(uxtheme, MAKEINTRESOURCEA(ordinal));\n    rt_not_null(proc);\n    return proc;\n}\n\nstatic void ui_theme_set_preferred_app_mode(int32_t mode) {\n    typedef BOOL (__stdcall *SetPreferredAppMode_t)(int32_t mode);\n    SetPreferredAppMode_t SetPreferredAppMode = (SetPreferredAppMode_t)\n            (SetPreferredAppMode_t)ui_theme_uxtheme_func(135);\n    errno_t r = rt_b2e(SetPreferredAppMode(mode));\n    // On Win11: 10.0.22631\n    // SetPreferredAppMode(true) failed 0x0000047E(1150) ERROR_OLD_WIN_VERSION\n    // \"The specified program requires a newer version of Windows.\"\n    if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n        rt_println(\"SetPreferredAppMode(AllowDark) failed %s\", rt_strerr(r));\n    }\n}\n\n// https://stackoverflow.com/questions/75835069/dark-system-contextmenu-in-window\n\nstatic void ui_theme_flush_menu_themes(void) {\n    typedef BOOL (__stdcall *FlushMenuThemes_t)(void);\n    FlushMenuThemes_t FlushMenuThemes = (FlushMenuThemes_t)\n            (FlushMenuThemes_t)ui_theme_uxtheme_func(136);\n    errno_t r = rt_b2e(FlushMenuThemes());\n    // FlushMenuThemes() works but returns ERROR_OLD_WIN_VERSION\n    // on newest Windows 11 but it is not documented thus no complains.\n    if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n        rt_println(\"FlushMenuThemes(AllowDark) failed %s\", rt_strerr(r));\n    }\n}\n\nstatic void ui_theme_allow_dark_mode_for_app(bool allow) {\n    // https://github.com/rizonesoft/Notepad3/tree/96a48bd829a3f3192bbc93cd6944cafb3228b96d/src/DarkMode\n    typedef BOOL (__stdcall *AllowDarkModeForApp_t)(bool allow);\n    AllowDarkModeForApp_t AllowDarkModeForApp =\n            (AllowDarkModeForApp_t)ui_theme_uxtheme_func(132);\n    if (AllowDarkModeForApp != null) {\n        errno_t r = rt_b2e(AllowDarkModeForApp(allow));\n        if (r != 0 && r != ERROR_PROC_NOT_FOUND) {\n            rt_println(\"AllowDarkModeForApp(true) failed %s\", rt_strerr(r));\n        }\n    }\n}\n\nstatic void ui_theme_allow_dark_mode_for_window(bool allow) {\n    typedef BOOL (__stdcall *AllowDarkModeForWindow_t)(HWND hWnd, bool allow);\n    AllowDarkModeForWindow_t AllowDarkModeForWindow =\n        (AllowDarkModeForWindow_t)ui_theme_uxtheme_func(133);\n    if (AllowDarkModeForWindow != null) {\n        int r = rt_b2e(AllowDarkModeForWindow((HWND)ui_app.window, allow));\n        // On Win11: 10.0.22631\n        // AllowDarkModeForWindow(true) failed 0x0000047E(1150) ERROR_OLD_WIN_VERSION\n        // \"The specified program requires a newer version of Windows.\"\n        if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n            rt_println(\"AllowDarkModeForWindow(true) failed %s\", rt_strerr(r));\n        }\n    }\n}\n\nstatic bool ui_theme_are_apps_dark(void) {\n    return !ui_theme_use_light_theme(\"AppsUseLightTheme\");\n}\n\nstatic bool ui_theme_is_system_dark(void) {\n    return !ui_theme_use_light_theme(\"SystemUsesLightTheme\");\n}\n\nstatic bool ui_theme_is_app_dark(void) {\n    if (ui_theme_dark < 0) { ui_theme_dark = ui_theme.are_apps_dark(); }\n    return ui_theme_dark;\n}\n\nstatic void ui_theme_refresh(void) {\n    rt_swear(ui_app.window != null);\n    ui_theme_dark = -1;\n    BOOL dark_mode = ui_theme_is_app_dark(); // must be 32-bit \"BOOL\"\n    static const DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20;\n    /* 20 == DWMWA_USE_IMMERSIVE_DARK_MODE in Windows 11 SDK.\n       This value was undocumented for Windows 10 versions 2004\n       and later, supported for Windows 11 Build 22000 and later. */\n    errno_t r = DwmSetWindowAttribute((HWND)ui_app.window,\n        DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_mode, sizeof(dark_mode));\n    if (r != 0) {\n        rt_println(\"DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) \"\n                \"failed %s\", rt_strerr(r));\n    }\n    ui_theme.allow_dark_mode_for_app(dark_mode);\n    ui_theme.allow_dark_mode_for_window(dark_mode);\n    ui_theme.set_preferred_app_mode(dark_mode ?\n        ui_theme_app_mode_force_dark : ui_theme_app_mode_force_light);\n    ui_theme.flush_menu_themes();\n    ui_app.request_layout();\n}\n\nui_theme_if ui_theme = {\n    .is_app_dark                  = ui_theme_is_app_dark,\n    .is_system_dark               = ui_theme_is_system_dark,\n    .are_apps_dark                = ui_theme_are_apps_dark,\n    .set_preferred_app_mode       = ui_theme_set_preferred_app_mode,\n    .flush_menu_themes            = ui_theme_flush_menu_themes,\n    .allow_dark_mode_for_app      = ui_theme_allow_dark_mode_for_app,\n    .allow_dark_mode_for_window   = ui_theme_allow_dark_mode_for_window,\n    .refresh                      = ui_theme_refresh,\n};\n\n\n// _______________________________ ui_toggle.c ________________________________\n\n#include \"rt/rt.h\"\n\nstatic void ui_toggle_paint_on_off(ui_view_t* v) {\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    int32_t x = v->x;\n    int32_t y = v->y + i.top;\n    ui_color_t c = ui_colors.darken(v->background,\n        !ui_theme.is_app_dark() ? 0.125f : 0.5f);\n    ui_color_t b = v->state.pressed ? ui_colors.tone_green : c;\n    const int32_t a = v->fm->ascent;\n    const int32_t d = v->fm->descent;\n    const int32_t w = v->fm->em.w;\n    int32_t r = ((a + d + 1) / 2) | 0x1; // radius must be odd\n    int32_t h = r * 2 + 1;\n    y += (v->h - i.top - i.bottom - h + 1) / 2;\n    y += r + 1; // because radius is odd\n    x += r;\n    ui_color_t border = ui_theme.is_app_dark() ?\n        ui_colors.darken(v->color, 0.5) :\n        ui_colors.lighten(v->color, 0.5);\n    if (v->state.hover) {\n        border = ui_colors.get_color(ui_color_id_hot_tracking);\n    }\n    ui_gdi.circle(x, y, r, border, b);\n    ui_gdi.circle(x + w - r, y, r, border, b);\n    ui_gdi.fill(x, y - r, w - r + 1, h, b);\n    ui_gdi.line(x, y - r, x + w - r + 1, y - r, border);\n    ui_gdi.line(x, y + r, x + w - r + 1, y + r, border);\n    int32_t x1 = v->state.pressed ? x + w - r : x;\n    // circle is too bold in control color - water it down\n    ui_color_t fill = ui_theme.is_app_dark() ?\n        ui_colors.darken(v->color, 0.5f) : ui_colors.lighten(v->color, 0.5f);\n    border = ui_theme.is_app_dark() ?\n        ui_colors.darken(fill, 0.0625f) : ui_colors.lighten(fill, 0.0625f);\n    ui_gdi.circle(x1, y, r - 2, border, fill);\n}\n\nstatic const char* ui_toggle_on_off_label(ui_view_t* v,\n        char* label, int32_t count)  {\n    rt_str.format(label, count, \"%s\", ui_view.string(v));\n    char* s = strstr(label, \"___\");\n    if (s != null) {\n        memcpy(s, v->state.pressed ? \"On \" : \"Off\", 3);\n    }\n    return rt_nls.str(label);\n}\n\nstatic void ui_toggle_measure(ui_view_t* v) {\n    if (v->min_w_em < 3.0f) {\n        rt_println(\"3.0f em minimum width\");\n        v->min_w_em = 4.0f;\n    }\n    ui_view.measure_control(v);\n    rt_assert(v->type == ui_view_toggle);\n}\n\nstatic void ui_toggle_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_toggle);\n    char txt[rt_countof(v->p.text)];\n    const char* label = ui_toggle_on_off_label(v, txt, rt_countof(txt));\n    const char* text = rt_nls.str(label);\n    ui_view.text_measure(v, text, &v->text);\n    ui_view.text_align(v, &v->text);\n    ui_toggle_paint_on_off(v);\n    const ui_color_t text_color = !v->state.hover ? v->color :\n            (ui_theme.is_app_dark() ? ui_colors.white : ui_colors.black);\n    const ui_gdi_ta_t ta = { .fm = v->fm, .color = text_color };\n    ui_gdi.text(&ta, v->x + v->text.xy.x, v->y + v->text.xy.y, \"%s\", text);\n}\n\nstatic void ui_toggle_flip(ui_toggle_t* t) {\n    ui_view.invalidate((ui_view_t*)t, null);\n    t->state.pressed = !t->state.pressed;\n    if (t->callback != null) { t->callback(t); }\n}\n\nstatic void ui_toggle_character(ui_view_t* v, const char* utf8) {\n    char ch = utf8[0];\n    if (ui_view.is_shortcut_key(v, ch)) {\n         ui_toggle_flip((ui_toggle_t*)v);\n    }\n}\n\nstatic bool ui_toggle_key_pressed(ui_view_t* v, int64_t key) {\n    const bool trigger = ui_app.alt && ui_view.is_shortcut_key(v, key);\n    if (trigger) { ui_toggle_flip((ui_toggle_t*)v); }\n    return trigger; // swallow if true\n}\n\nstatic bool ui_toggle_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (pressed && inside) { ui_toggle_flip((ui_toggle_t*)v); }\n    return pressed && inside;\n}\n\nvoid ui_view_init_toggle(ui_view_t* v) {\n    rt_assert(v->type == ui_view_toggle);\n    v->tap           = ui_toggle_tap;\n    v->paint         = ui_toggle_paint;\n    v->measure       = ui_toggle_measure;\n    v->character     = ui_toggle_character;\n    v->key_pressed   = ui_toggle_key_pressed;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    v->text_align    = ui.align.left;\n    if (v->debug.id == null) { v->debug.id = \"#toggle\"; }\n}\n\nvoid ui_toggle_init(ui_toggle_t* t, const char* label, fp32_t ems,\n       void (*callback)(ui_toggle_t* b)) {\n    ui_view.set_text(t, \"%s\", label);\n    t->min_w_em = ems;\n    t->callback = callback;\n    t->type = ui_view_toggle;\n    ui_view_init_toggle(t);\n}\n// ________________________________ ui_view.c _________________________________\n\n#include \"rt/rt.h\"\n\nstatic const fp64_t ui_view_hover_delay = 1.5; // seconds\n\n#pragma push_macro(\"ui_view_for_each\")\n\nstatic void ui_view_update_shortcut(ui_view_t* v);\n\n// adding and removing views is not expected to be frequent\n// actions by application code (human factor - UI design)\n// thus extra checks and verifications are there even in\n// release code because C is not type safety champion language.\n\nstatic inline void ui_view_check_type(ui_view_t* v) {\n    // little endian:\n    rt_static_assertion(('vwXX' & 0xFFFF0000U) == ('vwZZ' & 0xFFFF0000U));\n    rt_static_assertion((ui_view_stack & 0xFFFF0000U) == ('vwXX' & 0xFFFF0000U));\n    rt_swear(((uint32_t)v->type & 0xFFFF0000U) == ('vwXX'  & 0xFFFF0000U),\n          \"not a view: %4.4s 0x%08X (forgotten &static_view?)\",\n          &v->type, v->type);\n}\n\nstatic void ui_view_verify(ui_view_t* p) {\n    ui_view_check_type(p);\n    ui_view_for_each(p, c, {\n        ui_view_check_type(c);\n        ui_view_update_shortcut(c);\n        rt_swear(c->parent == p);\n        rt_swear(c == c->next->prev);\n        rt_swear(c == c->prev->next);\n    });\n}\n\nstatic ui_view_t* ui_view_add(ui_view_t* p, ...) {\n    va_list va;\n    va_start(va, p);\n    ui_view_t* c = va_arg(va, ui_view_t*);\n    while (c != null) {\n        rt_swear(c->parent == null && c->prev == null && c->next == null);\n        ui_view.add_last(p, c);\n        c = va_arg(va, ui_view_t*);\n    }\n    va_end(va);\n    ui_view_call_init(p);\n    ui_app.request_layout();\n    return p;\n}\n\nstatic void ui_view_add_first(ui_view_t* p, ui_view_t* c) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    c->parent = p;\n    if (p->child == null) {\n        c->prev = c;\n        c->next = c;\n    } else {\n        c->prev = p->child->prev;\n        c->next = p->child;\n        c->prev->next = c;\n        c->next->prev = c;\n    }\n    p->child = c;\n    ui_view_call_init(c);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_last(ui_view_t* p, ui_view_t* c) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    c->parent = p;\n    if (p->child == null) {\n        c->prev = c;\n        c->next = c;\n        p->child = c;\n    } else {\n        c->prev = p->child->prev;\n        c->next = p->child;\n        c->prev->next = c;\n        c->next->prev = c;\n    }\n    ui_view_call_init(c);\n    ui_view_verify(p);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_after(ui_view_t* c, ui_view_t* a) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    rt_not_null(a->parent);\n    c->parent = a->parent;\n    c->next = a->next;\n    c->prev = a;\n    a->next = c;\n    c->prev->next = c;\n    c->next->prev = c;\n    ui_view_call_init(c);\n    ui_view_verify(c->parent);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_before(ui_view_t* c, ui_view_t* b) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    rt_not_null(b->parent);\n    c->parent = b->parent;\n    c->prev = b->prev;\n    c->next = b;\n    b->prev = c;\n    c->prev->next = c;\n    c->next->prev = c;\n    ui_view_call_init(c);\n    ui_view_verify(c->parent);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_remove(ui_view_t* c) {\n    rt_not_null(c->parent);\n    rt_not_null(c->parent->child);\n    // if a view that has focus is removed from parent:\n    if (c == ui_app.focus) { ui_view.set_focus(null); }\n    if (c->prev == c) {\n        rt_swear(c->next == c);\n        c->parent->child = null;\n    } else {\n        c->prev->next = c->next;\n        c->next->prev = c->prev;\n        if (c->parent->child == c) {\n            c->parent->child = c->next;\n        }\n    }\n    c->prev = null;\n    c->next = null;\n    ui_view_verify(c->parent);\n    c->parent = null;\n    ui_app.request_layout();\n}\n\nstatic void ui_view_remove_all(ui_view_t* p) {\n    while (p->child != null) { ui_view.remove(p->child); }\n    ui_app.request_layout();\n}\n\nstatic void ui_view_disband(ui_view_t* p) {\n    // do not disband composite controls\n    if (p->type != ui_view_mbx && p->type != ui_view_slider) {\n        while (p->child != null) {\n            ui_view_disband(p->child);\n            ui_view.remove(p->child);\n        }\n    }\n    ui_app.request_layout();\n}\n\nstatic void ui_view_invalidate(const ui_view_t* v, const ui_rect_t* r) {\n    if (ui_view.is_hidden(v)) {\n        rt_println(\"hidden: %s\", ui_view_debug_id(v));\n    } else {\n        ui_rect_t rc = {0};\n        if (r != null) {\n            rc = (ui_rect_t){\n                .x = v->x + r->x,\n                .y = v->y + r->y,\n                .w = r->w,\n                .h = r->h\n            };\n        } else {\n            rc = (ui_rect_t){ v->x, v->y, v->w, v->h};\n            // expand view rectangle by padding\n            const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n            rc.x -= p.left;\n            rc.y -= p.top;\n            rc.w += p.left + p.right;\n            rc.h += p.top + p.bottom;\n        }\n        if (v->debug.trace.prc) {\n            rt_println(\"%d,%d %dx%d\", rc.x, rc.y, rc.w, rc.h);\n        }\n        ui_app.invalidate(&rc);\n    }\n}\n\nstatic const char* ui_view_string(ui_view_t* v) {\n    if (v->p.strid == 0) {\n        int32_t id = rt_nls.strid(v->p.text);\n        v->p.strid = id > 0 ? id : -1;\n    }\n    return v->p.strid < 0 ? v->p.text : // not localized\n        rt_nls.string(v->p.strid, v->p.text);\n}\n\nstatic ui_wh_t ui_view_text_metrics_va(int32_t x, int32_t y,\n        bool multiline, int32_t w, const ui_fm_t* fm,\n        const char* format, va_list va) {\n    const ui_gdi_ta_t ta = { .fm = fm, .color = ui_colors.transparent,\n                             .measure = true };\n    return multiline ?\n        ui_gdi.multiline_va(&ta, x, y, w, format, va) :\n        ui_gdi.text_va(&ta, x, y, format, va);\n}\n\nstatic ui_wh_t ui_view_text_metrics(int32_t x, int32_t y,\n        bool multiline, int32_t w, const ui_fm_t* fm,\n        const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_view_text_metrics_va(x, y, multiline, w, fm, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic void ui_view_text_measure(ui_view_t* v, const char* s,\n        ui_view_text_metrics_t* tm) {\n    const ui_fm_t* fm = v->fm;\n    tm->wh = (ui_wh_t){ .w = 0, .h = fm->height };\n    if (s[0] == 0) {\n        tm->multiline = false;\n    } else {\n        tm->multiline = strchr(s, '\\n') != null;\n        if (v->type == ui_view_label && tm->multiline) {\n            int32_t w = (int32_t)((fp64_t)v->min_w_em * (fp64_t)fm->em.w + 0.5);\n            tm->wh = ui_view.text_metrics(v->x, v->y, true,  w, fm, \"%s\", s);\n        } else {\n            tm->wh = ui_view.text_metrics(v->x, v->y, false, 0, fm, \"%s\", s);\n        }\n    }\n}\n\nstatic void ui_view_text_align(ui_view_t* v, ui_view_text_metrics_t* tm) {\n    tm->xy = (ui_point_t){ .x = -1, .y = -1 };\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // i_wh the inside insets w x h:\n    const ui_wh_t i_wh = { .w = v->w - i.left - i.right,\n                           .h = v->h - i.top - i.bottom };\n    const int32_t h_align = v->text_align & ~(ui.align.top|ui.align.bottom);\n    const int32_t v_align = v->text_align & ~(ui.align.left|ui.align.right);\n    tm->xy.x = i.left + (i_wh.w - tm->wh.w + 1) / 2;\n    if (h_align & ui.align.left) {\n        tm->xy.x = i.left;\n    } else if (h_align & ui.align.right) {\n        tm->xy.x = i_wh.w - tm->wh.w - i.right;\n    }\n    // vertical centering is trickier.\n    // mt.h is height of all measured lines of text\n    tm->xy.y = i.top + (i_wh.h - tm->wh.h + 1) / 2;\n    if (v_align & ui.align.top) {\n        tm->xy.y = i.top;\n    } else if (v_align & ui.align.bottom) {\n        tm->xy.y = i_wh.h - tm->wh.h - i.bottom;\n    } else if (!tm->multiline) {\n#if 0 // TODO: doesn't look good or right:\n        // UI controls should have x-height line in the dead center\n        // of the control to be visually balanced.\n        // y offset of \"x-line\" of the glyph:\n        const ui_fm_t* fm = v->fm;\n        const int32_t y_of_x_line = fm->baseline - fm->x_height;\n        // `dy` offset of the center to x-line (middle of glyph cell)\n        const int32_t dy = tm->wh.h / 2 - y_of_x_line;\n        tm->xy.y += dy / 2;\n        if (v->debug.trace.mt) {\n            rt_println(\" x-line: %d mt.h: %d mt.h / 2 - x_line: %d\",\n                      y_of_x_line, tm->wh.h, dy);\n        }\n#endif\n    }\n}\n\nstatic void ui_view_measure_control(ui_view_t* v) {\n    v->p.strid = 0;\n    const char* s = ui_view.string(v);\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    v->w = (int32_t)((fp64_t)fm->em.w * (fp64_t)v->min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    if (v->debug.trace.mt) {\n        const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n        rt_println(\">%dx%d em: %dx%d min: %.3fx%.3f \"\n                \"i: %d %d %d %d p: %d %d %d %d %s \\\"%.*s\\\"\",\n            v->w, v->h, fm->em.w, fm->em.h, v->min_w_em, v->min_h_em,\n            i.left, i.top, i.right, i.bottom,\n            p.left, p.top, p.right, p.bottom,\n            ui_view_debug_id(v),\n            rt_min(64, strlen(s)), s);\n        const ui_margins_t in = v->insets;\n        const ui_margins_t pd = v->padding;\n        rt_println(\" i: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\"\n                \" p: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\",\n            in.left, in.top, in.right, in.bottom,\n            in.left + in.right, in.top + in.bottom,\n            pd.left, pd.top, pd.right, pd.bottom,\n            pd.left + pd.right, pd.top + pd.bottom);\n    }\n    ui_view_text_measure(v, s, &v->text);\n    if (v->debug.trace.mt) {\n        rt_println(\" mt: %d %d\", v->text.wh.w, v->text.wh.h);\n    }\n    v->w = rt_max(v->w, i.left + v->text.wh.w + i.right);\n    v->h = rt_max(v->h, i.top  + v->text.wh.h + i.bottom);\n    ui_view_text_align(v, &v->text);\n    if (v->debug.trace.mt) {\n        rt_println(\"<%dx%d text_align x,y: %d,%d %s\",\n                v->w, v->h, v->text.xy.x, v->text.xy.y,\n                ui_view_debug_id(v));\n    }\n}\n\nstatic void ui_view_measure_children(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_for_each(v, c, { ui_view.measure(c); });\n    }\n}\n\nstatic void ui_view_measure(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_measure_children(v);\n        if (v->prepare != null) { v->prepare(v); }\n        if (v->measure != null && v->measure != ui_view_measure) {\n            v->measure(v);\n        } else {\n            ui_view.measure_control(v);\n        }\n        if (v->measured != null) { v->measured(v); }\n    }\n}\n\nstatic void ui_layout_view(ui_view_t* rt_unused(v)) {\n//  ui_ltrb_t i = ui_view.margins(v, &v->insets);\n//  ui_ltrb_t p = ui_view.margins(v, &v->padding);\n//  rt_println(\">%s %d,%d %dx%d p: %d %d %d %d  i: %d %d %d %d\",\n//               v->p.text, v->x, v->y, v->w, v->h,\n//               p.left, p.top, p.right, p.bottom,\n//               i.left, i.top, i.right, i.bottom);\n//  rt_println(\"<%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n}\n\nstatic void ui_view_layout_children(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_for_each(v, c, { ui_view.layout(c); });\n    }\n}\n\nstatic void ui_view_layout(ui_view_t* v) {\n//  rt_println(\">%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n    if (!ui_view.is_hidden(v)) {\n        if (v->layout != null && v->layout != ui_view_layout) {\n            v->layout(v);\n        } else {\n            ui_layout_view(v);\n        }\n        if (v->composed != null) { v->composed(v); }\n        ui_view_layout_children(v);\n    }\n//  rt_println(\"<%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n}\n\nstatic bool ui_view_inside(const ui_view_t* v, const ui_point_t* pt) {\n    const int32_t x = pt->x - v->x;\n    const int32_t y = pt->y - v->y;\n    return 0 <= x && x < v->w && 0 <= y && y < v->h;\n}\n\nstatic bool ui_view_is_parent_of(const ui_view_t* parent,\n        const ui_view_t* child) {\n    rt_swear(parent != null && child != null);\n    const ui_view_t* p = child->parent;\n    while (p != null) {\n        if (parent == p) { return true; }\n        p = p->parent;\n    }\n    return false;\n}\n\nstatic ui_ltrb_t ui_view_margins(const ui_view_t* v, const ui_margins_t* m) {\n    const fp64_t gw = (fp64_t)m->left + (fp64_t)m->right;\n    const fp64_t gh = (fp64_t)m->top  + (fp64_t)m->bottom;\n    const ui_wh_t* em = &v->fm->em;\n    const int32_t em_w = (int32_t)(em->w * gw + 0.5);\n    const int32_t em_h = (int32_t)(em->h * gh + 0.5);\n    const int32_t left = (int32_t)((fp64_t)em->w * (fp64_t)m->left + 0.5);\n    const int32_t top  = (int32_t)((fp64_t)em->h * (fp64_t)m->top  + 0.5);\n    return (ui_ltrb_t) {\n        .left   = left,         .top    = top,\n        .right  = em_w - left,  .bottom = em_h - top\n    };\n}\n\nstatic void ui_view_inbox(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* insets) {\n    rt_swear(r != null || insets != null);\n    rt_swear(v->max_w >= 0 && v->max_h >= 0);\n    const ui_ltrb_t i = ui_view_margins(v, &v->insets);\n    if (insets != null) { *insets = i; }\n    if (r != null) {\n        *r = (ui_rect_t) {\n            .x = v->x + i.left,\n            .y = v->y + i.top,\n            .w = v->w - i.left - i.right,\n            .h = v->h - i.top  - i.bottom,\n        };\n    }\n}\n\nstatic void ui_view_outbox(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* padding) {\n    rt_swear(r != null || padding != null);\n    rt_swear(v->max_w >= 0 && v->max_h >= 0);\n    const ui_ltrb_t p = ui_view_margins(v, &v->padding);\n    if (padding != null) { *padding = p; }\n    if (r != null) {\n//      rt_println(\"%s %d,%d %dx%d %.1f %.1f %.1f %.1f\", v->p.text,\n//          v->x, v->y, v->w, v->h,\n//          v->padding.left, v->padding.top, v->padding.right, v->padding.bottom);\n        *r = (ui_rect_t) {\n            .x = v->x - p.left,\n            .y = v->y - p.top,\n            .w = v->w + p.left + p.right,\n            .h = v->h + p.top  + p.bottom,\n        };\n//      rt_println(\"%s %d,%d %dx%d\", v->p.text,\n//          r->x, r->y, r->w, r->h);\n    }\n}\n\nstatic void ui_view_update_shortcut(ui_view_t* v) {\n    if (ui_view.is_control(v) && v->type != ui_view_text &&\n        v->shortcut == 0x00) {\n        const char* s = ui_view.string(v);\n        const char* a = strchr(s, '&');\n        if (a != null && a[1] != 0 && a[1] != '&') {\n            // TODO: utf-8 shortcuts? possible\n            v->shortcut = a[1];\n        }\n    }\n}\n\nstatic void ui_view_set_text_va(ui_view_t* v, const char* format, va_list va) {\n    char t[rt_countof(v->p.text)];\n    rt_str.format_va(t, rt_countof(t), format, va);\n    char* s = v->p.text;\n    if (strcmp(s, t) != 0) {\n        int32_t n = (int32_t)strlen(t);\n        memcpy(s, t, (size_t)n + 1);\n        v->p.strid = 0;  // next call to nls() will localize it\n        ui_view_update_shortcut(v);\n        ui_app.request_layout();\n    }\n}\n\nstatic void ui_view_set_text(ui_view_t* v, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_view.set_text_va(v, format, va);\n    va_end(va);\n}\n\nstatic void ui_view_show_hint(ui_view_t* v, ui_view_t* hint) {\n    ui_view_call_init(hint);\n    ui_view.set_text(hint, v->hint);\n    ui_view.measure(hint);\n    int32_t x = v->x + v->w / 2 - hint->w / 2 + hint->fm->em.w / 4;\n    int32_t y = v->y + v->h + hint->fm->em.h / 4;\n    if (x + hint->w > ui_app.crc.w) {\n        x = ui_app.crc.w - hint->w - hint->fm->em.w / 2;\n    }\n    if (x < 0) { x = hint->fm->em.w / 2; }\n    if (y + hint->h > ui_app.crc.h) {\n        y = ui_app.crc.h - hint->h - hint->fm->em.h / 2;\n    }\n    if (y < 0) { y = hint->fm->em.h / 2; }\n    // show_tooltip will center horizontally\n    ui_app.show_hint(hint, x + hint->w / 2, y, 0);\n}\n\nstatic void ui_view_hovering(ui_view_t* v, bool start) {\n    static ui_label_t hint = ui_label(0.0, \"\");\n    if (start && ui_app.animating.view == null && v->hint[0] != 0 &&\n       !ui_view.is_hidden(v)) {\n        hint.padding = (ui_margins_t){0, 0, 0, 0};\n        hint.parent = ui_app.content;\n        hint.state.hidden = false;\n        ui_view_show_hint(v, &hint);\n    } else if (!start && ui_app.animating.view == &hint) {\n        ui_app.show_hint(null, -1, -1, 0);\n    }\n}\n\nstatic bool ui_view_is_shortcut_key(ui_view_t* v, int64_t key) {\n    // Supported keyboard shortcuts are ASCII characters only for now\n    // If there is not focused UI control in Alt+key [Alt] is optional.\n    // If there is focused control only Alt+Key is accepted as shortcut\n    char ch = 0x20 <= key && key <= 0x7F ? (char)toupper((char)key) : 0x00;\n    bool needs_alt = ui_app.focus != null && ui_app.focus != v &&\n         !ui_view.is_parent_of(ui_app.focus, v);\n    bool keyboard_shortcut = ch != 0x00 && v->shortcut != 0x00 &&\n         (ui_app.alt || ui_app.ctrl || !needs_alt) && toupper(v->shortcut) == ch;\n    return keyboard_shortcut;\n}\n\nstatic bool ui_view_is_orphan(const ui_view_t* v) {\n    while (v != ui_app.root && v != null) { v = v->parent; }\n    return v == null;\n}\n\nstatic bool ui_view_is_hidden(const ui_view_t* v) {\n    bool hidden = v->state.hidden || ui_view.is_orphan(v);\n    while (!hidden && v->parent != null) {\n        v = v->parent;\n        hidden = v->state.hidden;\n    }\n    return hidden;\n}\n\nstatic bool ui_view_is_disabled(const ui_view_t* v) {\n    bool disabled = v->state.disabled;\n    while (!disabled && v->parent != null) {\n        v = v->parent;\n        disabled = v->state.disabled;\n    }\n    return disabled;\n}\n\nstatic void ui_view_timer(ui_view_t* v, ui_timer_t id) {\n    if (v->timer != null) { v->timer(v, id); }\n    // timers are delivered even to hidden and disabled views:\n    ui_view_for_each(v, c, { ui_view_timer(c, id); });\n}\n\nstatic void ui_view_every_sec(ui_view_t* v) {\n    if (v->every_sec != null) { v->every_sec(v); }\n    ui_view_for_each(v, c, { ui_view_every_sec(c); });\n}\n\nstatic void ui_view_every_100ms(ui_view_t* v) {\n    if (v->every_100ms != null) { v->every_100ms(v); }\n    ui_view_for_each(v, c, { ui_view_every_100ms(c); });\n}\n\nstatic bool ui_view_key_pressed(ui_view_t* v, int64_t k) {\n    bool done = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->key_pressed != null) {\n            ui_view_update_shortcut(v);\n            done = v->key_pressed(v, k);\n        }\n        if (!done) {\n            ui_view_for_each(v, c, {\n                done = ui_view_key_pressed(c, k);\n                if (done) { break; }\n            });\n        }\n    }\n    return done;\n}\n\nstatic bool ui_view_key_released(ui_view_t* v, int64_t k) {\n    bool done = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->key_released != null) {\n            done = v->key_released(v, k);\n        }\n        if (!done) {\n            ui_view_for_each(v, c, {\n                done = ui_view_key_released(c, k);\n                if (done) { break; }\n            });\n        }\n    }\n    return done;\n}\n\nstatic void ui_view_character(ui_view_t* v, const char* utf8) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->character != null) {\n            ui_view_update_shortcut(v);\n            v->character(v, utf8);\n        }\n        ui_view_for_each(v, c, { ui_view_character(c, utf8); });\n    }\n}\n\nstatic void ui_view_resolve_color_ids(ui_view_t* v) {\n    if (v->color_id > 0) {\n        v->color = ui_colors.get_color(v->color_id);\n    }\n    if (v->background_id > 0) {\n        v->background = ui_colors.get_color(v->background_id);\n    }\n}\n\nstatic void ui_view_paint(ui_view_t* v) {\n    rt_assert(ui_app.crc.w > 0 && ui_app.crc.h > 0);\n    ui_view_resolve_color_ids(v);\n    if (v->debug.trace.prc) {\n        const char* s = ui_view.string(v);\n        rt_println(\"%d,%d %dx%d prc: %d,%d %dx%d \\\"%.*s\\\"\", v->x, v->y, v->w, v->h,\n                ui_app.prc.x, ui_app.prc.y, ui_app.prc.w, ui_app.prc.h,\n                rt_min(64, strlen(s)), s);\n    }\n    if (!v->state.hidden && ui_app.crc.w > 0 && ui_app.crc.h > 0) {\n        if (v->erase   != null) { v->erase(v); }\n        if (v->paint   != null) { v->paint(v); }\n        if (v->painted != null) { v->painted(v); }\n        if (v->debug.paint.margins) { ui_view.debug_paint_margins(v); }\n        if (v->debug.paint.fm)   { ui_view.debug_paint_fm(v); }\n        if (v->debug.paint.call && v->debug_paint != null) { v->debug_paint(v); }\n        ui_view_for_each(v, c, { ui_view_paint(c); });\n    }\n}\n\nstatic bool ui_view_has_focus(const ui_view_t* v) {\n    return ui_app.focused() && ui_app.focus == v;\n}\n\nstatic void ui_view_set_focus(ui_view_t* v) {\n    if (ui_app.focus != v) {\n        ui_view_t* loosing = ui_app.focus;\n        ui_view_t* gaining = v;\n        if (gaining != null) {\n            rt_swear(gaining->focusable && !ui_view.is_hidden(gaining) &&\n                                        !ui_view.is_disabled(gaining));\n        }\n        if (loosing != null) { rt_swear(loosing->focusable); }\n        ui_app.focus = v;\n        if (loosing != null && loosing->focus_lost != null) {\n            loosing->focus_lost(loosing);\n        }\n        if (gaining != null && gaining->focus_gained != null) {\n            gaining->focus_gained(gaining);\n        }\n    }\n}\n\nstatic int64_t ui_view_hit_test(const ui_view_t* v, ui_point_t pt) {\n    int64_t ht = ui.hit_test.nowhere;\n    if (!ui_view.is_hidden(v) && v->hit_test != null) {\n         ht = v->hit_test(v, pt);\n    }\n    if (ht == ui.hit_test.nowhere) {\n        ui_view_for_each(v, c, {\n            if (!c->state.hidden && ui_view.inside(c, &pt)) {\n                ht = ui_view_hit_test(c, pt);\n                if (ht != ui.hit_test.nowhere) { break; }\n            }\n        });\n    }\n    return ht;\n}\n\nstatic void ui_view_update_hover(ui_view_t* v, bool hidden) {\n    const bool hover  = v->state.hover;\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    v->state.hover = !ui_view.is_hidden(v) && inside;\n    if (hover != v->state.hover) {\n//      rt_println(\"hover := %d %p %s\", v->state.hover, v, ui_view_debug_id(v));\n        ui_view.hover_changed(v); // even for hidden\n        if (!hidden) { ui_view.invalidate(v, null); }\n    }\n}\n\nstatic void ui_view_mouse_hover(ui_view_t* v) {\n//  rt_println(\"%d,%d %s\", ui_app.mouse.x, ui_app.mouse.y,\n//          ui_app.mouse_left  ? \"L\" : \"_\",\n//          ui_app.mouse_right ? \"R\" : \"_\");\n    // mouse hover over is dispatched even to disabled views\n    const bool hidden = ui_view.is_hidden(v);\n    ui_view_update_hover(v, hidden);\n    if (!hidden && v->mouse_hover != null) { v->mouse_hover(v); }\n    ui_view_for_each(v, c, { ui_view_mouse_hover(c); });\n}\n\nstatic void ui_view_mouse_move(ui_view_t* v) {\n//  rt_println(\"%d,%d %s\", ui_app.mouse.x, ui_app.mouse.y,\n//          ui_app.mouse_left  ? \"L\" : \"_\",\n//          ui_app.mouse_right ? \"R\" : \"_\");\n    // mouse move is dispatched even to disabled views\n    const bool hidden = ui_view.is_hidden(v);\n    ui_view_update_hover(v, hidden);\n    if (!hidden && v->mouse_move != null) { v->mouse_move(v); }\n    ui_view_for_each(v, c, { ui_view_mouse_move(c); });\n}\n\nstatic void ui_view_double_click(ui_view_t* v, int32_t ix) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (inside) {\n            if (v->focusable) { ui_view.set_focus(v); }\n            if (v->double_click != null) { v->double_click(v, ix); }\n        }\n        ui_view_for_each(v, c, { ui_view_double_click(c, ix); });\n    }\n}\n\nstatic void ui_view_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->mouse_scroll != null) { v->mouse_scroll(v, dx_dy); }\n        ui_view_for_each(v, c, { ui_view_mouse_scroll(c, dx_dy); });\n    }\n}\n\nstatic void ui_view_hover_changed(ui_view_t* v) {\n    if (!v->state.hidden) {\n        if (!v->state.hover) {\n            v->p.hover_when = 0;\n            ui_view.hovering(v, false); // cancel hover\n        } else {\n            rt_swear(ui_view_hover_delay >= 0);\n            if (v->p.hover_when >= 0) {\n                v->p.hover_when = ui_app.now + ui_view_hover_delay;\n            }\n        }\n    }\n}\n\nstatic void ui_view_lose_hidden_focus(ui_view_t* v) {\n    // removes focus from hidden or disabled ui controls\n    if (ui_app.focus != null) {\n        if (ui_app.focus == v && (v->state.disabled || v->state.hidden)) {\n            ui_view.set_focus(null);\n        } else {\n            ui_view_for_each(v, c, {\n                if (ui_app.focus != null) { ui_view_lose_hidden_focus(c); }\n            });\n        }\n    }\n}\n\nstatic bool ui_view_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_tap(c, ix, pressed);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && pressed && inside) {\n            if (v->focusable) { ui_view.set_focus(v); }\n            if (v->tap != null) { swallow = v->tap(v, ix, pressed); }\n        }\n        if (!swallow && !pressed) {\n            // mouse click release is never swallowed because a lot\n            // of controls want to hear it:\n            if (v->tap != null) { (void)v->tap(v, ix, pressed); }\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_long_press(ui_view_t* v, int32_t ix) {\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_long_press(c, ix);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->long_press != null) {\n            swallow = v->long_press(v, ix);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_double_tap(ui_view_t* v, int32_t ix) { // 0: left 1: middle 2: right\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_double_tap(c, ix);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->double_tap != null) {\n            swallow = v->double_tap(v, ix);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_context_menu(ui_view_t* v) {\n    bool swallow = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_context_menu(c);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->context_menu != null) {\n            swallow = v->context_menu(v);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_message(ui_view_t* view, int32_t m, int64_t wp, int64_t lp,\n        int64_t* ret) {\n    if (!view->state.hidden) {\n        if (view->p.hover_when > 0 && ui_app.now > view->p.hover_when) {\n            view->p.hover_when = -1; // \"already called\"\n            ui_view.hovering(view, true);\n        }\n    }\n    // message() callback is called even for hidden and disabled views\n    // could be useful for enabling conditions of post() messages from\n    // background rt_thread.\n    if (view->message != null) {\n        if (view->message(view, m, wp, lp, ret)) { return true; }\n    }\n    ui_view_for_each(view, c, {\n        if (ui_view_message(c, m, wp, lp, ret)) { return true; }\n    });\n    return false;\n}\n\nstatic bool ui_view_is_container(const ui_view_t* v) {\n    return  v->type == ui_view_stack ||\n            v->type == ui_view_span  ||\n            v->type == ui_view_list;\n}\n\nstatic bool ui_view_is_spacer(const ui_view_t* v) {\n    return  v->type == ui_view_spacer;\n}\n\nstatic bool ui_view_is_control(const ui_view_t* v) {\n    return  v->type == ui_view_text   ||\n            v->type == ui_view_label  ||\n            v->type == ui_view_toggle ||\n            v->type == ui_view_button ||\n            v->type == ui_view_slider ||\n            v->type == ui_view_mbx;\n}\n\nstatic void ui_view_debug_paint_margins(ui_view_t* v) {\n    if (v->debug.paint.margins) {\n        if (v->type == ui_view_spacer) {\n            ui_gdi.fill(v->x, v->y, v->w, v->h, ui_color_rgb(128, 128, 128));\n        }\n        const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        ui_color_t c = ui_colors.green;\n        const int32_t pl = p.left;\n        const int32_t pr = p.right;\n        const int32_t pt = p.top;\n        const int32_t pb = p.bottom;\n        if (pl > 0) { ui_gdi.frame(v->x - pl, v->y, pl, v->h, c); }\n        if (pr > 0) { ui_gdi.frame(v->x + v->w, v->y, pr, v->h, c); }\n        if (pt > 0) { ui_gdi.frame(v->x, v->y - pt, v->w, pt, c); }\n        if (p.bottom > 0) {\n            ui_gdi.frame(v->x, v->y + v->h, v->w, pb, c);\n        }\n        c = ui_colors.orange;\n        const int32_t il = i.left;\n        const int32_t ir = i.right;\n        const int32_t it = i.top;\n        const int32_t ib = i.bottom;\n        if (il > 0) { ui_gdi.frame(v->x, v->y, il, v->h, c); }\n        if (ir > 0) { ui_gdi.frame(v->x + v->w - ir, v->y, ir, v->h, c); }\n        if (it > 0) { ui_gdi.frame(v->x, v->y, v->w, it, c); }\n        if (ib > 0) { ui_gdi.frame(v->x, v->y + v->h - ib, v->w, ib, c); }\n        if ((ui_view.is_container(v) || ui_view.is_spacer(v)) &&\n            v->w > 0 && v->h > 0) {\n            ui_wh_t wh = ui_view_text_metrics(v->x, v->y, false, 0,\n                                              v->fm, \"%s\", ui_view.string(v));\n            const int32_t tx = v->x;\n            const int32_t ty = v->y + v->h - wh.h;\n            const ui_gdi_ta_t ta = { .fm = v->fm, .color = ui_colors.red };\n            ui_gdi.text(&ta, tx, ty, \"%s %d,%d %dx%d\", ui_view_debug_id(v),\n                        v->x, v->y, v->w, v->h);\n        }\n    }\n}\n\nstatic void ui_view_debug_paint_fm(ui_view_t* v) {\n    if (v->debug.paint.fm && v->p.text[0] != 0 &&\n       !ui_view_is_container(v) && !ui_view_is_spacer(v)) {\n        const ui_point_t t = v->text.xy;\n        const int32_t x = v->x;\n        const int32_t y = v->y;\n        const int32_t w = v->w;\n        const int32_t y_0 = y + t.y;\n        const int32_t y_b = y_0 + v->fm->baseline;\n        const int32_t y_a = y_b - v->fm->ascent;\n        const int32_t y_h = y_0 + v->fm->height;\n        const int32_t y_x = y_b - v->fm->x_height;\n        const int32_t y_d = y_b + v->fm->descent;\n        // fm.height y == 0 line is painted one pixel higher:\n        ui_gdi.line(x, y_0 - 1, x + w, y_0 - 1, ui_colors.red);\n        ui_gdi.line(x, y_a, x + w, y_a, ui_colors.green);\n        ui_gdi.line(x, y_x, x + w, y_x, ui_colors.orange);\n        ui_gdi.line(x, y_b, x + w, y_b, ui_colors.red);\n        ui_gdi.line(x, y_d, x + w, y_d, ui_colors.green);\n        if (y_h != y_d) {\n            ui_gdi.line(x, y_d, x + w, y_d, ui_colors.green);\n            ui_gdi.line(x, y_h, x + w, y_h, ui_colors.red);\n        } else {\n            ui_gdi.line(x, y_h, x + w, y_h, ui_colors.orange);\n        }\n        // fm.height line painted _under_ the actual height\n    }\n}\n\n#pragma push_macro(\"ui_view_no_siblings\")\n\n#define ui_view_no_siblings(v) do {                    \\\n    rt_swear((v)->parent == null && (v)->child == null && \\\n          (v)->prev == null && (v)->next == null);     \\\n} while (0)\n\nstatic void ui_view_test(void) {\n    ui_view_t p0 = ui_view(stack);\n    ui_view_t c1 = ui_view(stack);\n    ui_view_t c2 = ui_view(stack);\n    ui_view_t c3 = ui_view(stack);\n    ui_view_t c4 = ui_view(stack);\n    ui_view_t g1 = ui_view(stack);\n    ui_view_t g2 = ui_view(stack);\n    ui_view_t g3 = ui_view(stack);\n    ui_view_t g4 = ui_view(stack);\n    // add grand children to children:\n    ui_view.add(&c2, &g1, &g2, null);               ui_view_verify(&c2);\n    ui_view.add(&c3, &g3, &g4, null);               ui_view_verify(&c3);\n    // single child\n    ui_view.add(&p0, &c1, null);                    ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    // two children\n    ui_view.add(&p0, &c1, &c2, null);               ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    // three children\n    ui_view.add(&p0, &c1, &c2, &c3, null);          ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    ui_view.remove(&c3);                            ui_view_verify(&p0);\n    // add_first, add_last, add_before, add_after\n    ui_view.add_first(&p0, &c1);                    ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    ui_view.add_last(&p0, &c4);                     ui_view_verify(&p0);\n    rt_swear(p0.child == &c1 && p0.child->prev == &c4);\n    ui_view.add_after(&c2, &c1);                    ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    rt_swear(c1.next == &c2);\n    ui_view.add_before(&c3, &c4);                   ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    rt_swear(c4.prev == &c3);\n    // removing all\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    ui_view.remove(&c3);                            ui_view_verify(&p0);\n    ui_view.remove(&c4);                            ui_view_verify(&p0);\n    ui_view_no_siblings(&p0);\n    ui_view_no_siblings(&c1);\n    ui_view_no_siblings(&c4);\n    ui_view.remove(&g1);                            ui_view_verify(&c2);\n    ui_view.remove(&g2);                            ui_view_verify(&c2);\n    ui_view.remove(&g3);                            ui_view_verify(&c3);\n    ui_view.remove(&g4);                            ui_view_verify(&c3);\n    ui_view_no_siblings(&c2); ui_view_no_siblings(&c3);\n    ui_view_no_siblings(&g1); ui_view_no_siblings(&g2);\n    ui_view_no_siblings(&g3); ui_view_no_siblings(&g4);\n    // a bit more intuitive (for a human) nested way to initialize tree:\n    ui_view.add(&p0,\n        &c1,\n        ui_view.add(&c2, &g1, &g2, null),\n        ui_view.add(&c3, &g3, &g4, null),\n        &c4);\n    ui_view_verify(&p0);\n    ui_view_disband(&p0);\n    ui_view_no_siblings(&p0);\n    ui_view_no_siblings(&c1); ui_view_no_siblings(&c2);\n    ui_view_no_siblings(&c3); ui_view_no_siblings(&c4);\n    ui_view_no_siblings(&g1); ui_view_no_siblings(&g2);\n    ui_view_no_siblings(&g3); ui_view_no_siblings(&g4);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"ui_view_no_siblings\")\n\nui_view_if ui_view = {\n    .add                 = ui_view_add,\n    .add_first           = ui_view_add_first,\n    .add_last            = ui_view_add_last,\n    .add_after           = ui_view_add_after,\n    .add_before          = ui_view_add_before,\n    .remove              = ui_view_remove,\n    .remove_all          = ui_view_remove_all,\n    .disband             = ui_view_disband,\n    .inside              = ui_view_inside,\n    .is_parent_of        = ui_view_is_parent_of,\n    .margins             = ui_view_margins,\n    .inbox               = ui_view_inbox,\n    .outbox              = ui_view_outbox,\n    .set_text            = ui_view_set_text,\n    .set_text_va         = ui_view_set_text_va,\n    .invalidate          = ui_view_invalidate,\n    .text_metrics_va     = ui_view_text_metrics_va,\n    .text_metrics        = ui_view_text_metrics,\n    .text_measure        = ui_view_text_measure,\n    .text_align          = ui_view_text_align,\n    .measure_control     = ui_view_measure_control,\n    .measure_children    = ui_view_measure_children,\n    .layout_children     = ui_view_layout_children,\n    .measure             = ui_view_measure,\n    .layout              = ui_view_layout,\n    .string              = ui_view_string,\n    .is_orphan           = ui_view_is_orphan,\n    .is_hidden           = ui_view_is_hidden,\n    .is_disabled         = ui_view_is_disabled,\n    .is_control          = ui_view_is_control,\n    .is_container        = ui_view_is_container,\n    .is_spacer           = ui_view_is_spacer,\n    .timer               = ui_view_timer,\n    .every_sec           = ui_view_every_sec,\n    .every_100ms         = ui_view_every_100ms,\n    .hit_test            = ui_view_hit_test,\n    .key_pressed         = ui_view_key_pressed,\n    .key_released        = ui_view_key_released,\n    .character           = ui_view_character,\n    .paint               = ui_view_paint,\n    .has_focus           = ui_view_has_focus,\n    .set_focus           = ui_view_set_focus,\n    .lose_hidden_focus   = ui_view_lose_hidden_focus,\n    .mouse_hover         = ui_view_mouse_hover,\n    .mouse_move          = ui_view_mouse_move,\n    .mouse_scroll        = ui_view_mouse_scroll,\n    .hovering            = ui_view_hovering,\n    .hover_changed       = ui_view_hover_changed,\n    .is_shortcut_key     = ui_view_is_shortcut_key,\n    .context_menu        = ui_view_context_menu,\n    .tap                 = ui_view_tap,\n    .long_press          = ui_view_long_press,\n    .double_tap          = ui_view_double_tap,\n    .message             = ui_view_message,\n    .debug_paint_margins = ui_view_debug_paint_margins,\n    .debug_paint_fm      = ui_view_debug_paint_fm,\n    .test                = ui_view_test\n};\n\n#ifdef UI_VIEW_TEST\n\nrt_static_init(ui_view) {\n    ui_view.test();\n}\n\n#endif\n\n#pragma pop_macro(\"ui_view_for_each\")\n\n#endif // ui_implementation\n\n"
  },
  {
    "path": "src/rt/rt.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n// #include \"ut/macos.h\" // TODO\n// #include \"ut/linux.h\" // TODO\n\n"
  },
  {
    "path": "src/rt/rt_args.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic void* rt_args_memory;\n\nstatic void rt_args_main(int32_t argc, const char* argv[], const char** env) {\n    rt_swear(rt_args.c == 0 && rt_args.v == null && rt_args.env == null);\n    rt_swear(rt_args_memory == null);\n    rt_args.c = argc;\n    rt_args.v = argv;\n    rt_args.env = env;\n}\n\nstatic int32_t rt_args_option_index(const char* option) {\n    for (int32_t i = 1; i < rt_args.c; i++) {\n        if (strcmp(rt_args.v[i], \"--\") == 0) { break; } // no options after '--'\n        if (strcmp(rt_args.v[i], option) == 0) { return i; }\n    }\n    return -1;\n}\n\nstatic void rt_args_remove_at(int32_t ix) {\n    // returns new argc\n    rt_assert(0 < rt_args.c);\n    rt_assert(0 < ix && ix < rt_args.c); // cannot remove rt_args.v[0]\n    for (int32_t i = ix; i < rt_args.c; i++) {\n        rt_args.v[i] = rt_args.v[i + 1];\n    }\n    rt_args.v[rt_args.c - 1] = \"\";\n    rt_args.c--;\n}\n\nstatic bool rt_args_option_bool(const char* option) {\n    int32_t ix = rt_args_option_index(option);\n    if (ix > 0) { rt_args_remove_at(ix); }\n    return ix > 0;\n}\n\nstatic bool rt_args_option_int(const char* option, int64_t *value) {\n    int32_t ix = rt_args_option_index(option);\n    if (ix > 0 && ix < rt_args.c - 1) {\n        const char* s = rt_args.v[ix + 1];\n        int32_t base = (strstr(s, \"0x\") == s || strstr(s, \"0X\") == s) ? 16 : 10;\n        const char* b = s + (base == 10 ? 0 : 2);\n        char* e = null;\n        errno = 0;\n        int64_t v = strtoll(b, &e, base);\n        if (errno == 0 && e > b && *e == 0) {\n            *value = v;\n        } else {\n            ix = -1;\n        }\n    } else {\n        ix = -1;\n    }\n    if (ix > 0) {\n        rt_args_remove_at(ix); // remove option\n        rt_args_remove_at(ix); // remove following number\n    }\n    return ix > 0;\n}\n\nstatic const char* rt_args_option_str(const char* option) {\n    int32_t ix = rt_args_option_index(option);\n    const char* s = null;\n    if (ix > 0 && ix < rt_args.c - 1) {\n        s = rt_args.v[ix + 1];\n    } else {\n        ix = -1;\n    }\n    if (ix > 0) {\n        rt_args_remove_at(ix); // remove option\n        rt_args_remove_at(ix); // remove following string\n    }\n    return ix > 0 ? s : null;\n}\n\n// Terminology: \"quote\" in the code and comments below\n// actually refers to \"fp64_t quote mark\" and used for brevity.\n\n// TODO: posix like systems\n// Looks like all shells support quote marks but\n// AFAIK MacOS bash and zsh also allow (and prefer) backslash escaped\n// space character. Unclear what other escaping shell and posix compliant\n// parser should support.\n// Lengthy discussion here:\n// https://stackoverflow.com/questions/1706551/parse-string-into-argv-argc\n\n// Microsoft specific argument parsing:\n// https://web.archive.org/web/20231115181633/http://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments?view=msvc-170\n// Alternative: just use CommandLineToArgvW()\n\ntypedef struct { const char* s; char* d; const char* e; } rt_args_pair_t;\n\nstatic rt_args_pair_t rt_args_parse_backslashes(rt_args_pair_t p) {\n    enum { quote = '\"', backslash = '\\\\' };\n    const char* s = p.s;\n    char* d = p.d;\n    rt_swear(*s == backslash);\n    int32_t bsc = 0; // number of backslashes\n    while (*s == backslash) { s++; bsc++; }\n    if (*s == quote) {\n        while (bsc > 1 && d < p.e) { *d++ = backslash; bsc -= 2; }\n        if (bsc == 1 && d < p.e) { *d++ = *s++; }\n    } else {\n        // Backslashes are interpreted literally,\n        // unless they immediately precede a quote:\n        while (bsc > 0 && d < p.e) { *d++ = backslash; bsc--; }\n    }\n    return (rt_args_pair_t){ .s = s, .d = d, .e = p.e };\n}\n\nstatic rt_args_pair_t rt_args_parse_quoted(rt_args_pair_t p) {\n    enum { quote = '\"', backslash = '\\\\' };\n    const char* s = p.s;\n    char* d = p.d;\n    rt_swear(*s == quote);\n    s++; // opening quote (skip)\n    while (*s != 0x00) {\n        if (*s == backslash) {\n            p = rt_args_parse_backslashes((rt_args_pair_t){\n                        .s = s, .d = d, .e = p.e });\n            s = p.s; d = p.d;\n        } else if (*s == quote && s[1] == quote) {\n            // Within a quoted string, a pair of quote is\n            // interpreted as a single escaped quote.\n            if (d < p.e) { *d++ = *s++; }\n            s++; // 1 for 2 quotes\n        } else if (*s == quote) {\n            s++; // closing quote (skip)\n            break;\n        } else if (d < p.e) {\n            *d++ = *s++;\n        }\n    }\n    return (rt_args_pair_t){ .s = s, .d = d, .e = p.e };\n}\n\nstatic void rt_args_parse(const char* s) {\n    rt_swear(s[0] != 0, \"cannot parse empty string\");\n    rt_swear(rt_args.c == 0);\n    rt_swear(rt_args.v == null);\n    rt_swear(rt_args_memory == null);\n    enum { quote = '\"', backslash = '\\\\', tab = '\\t', space = 0x20 };\n    const int32_t len = (int32_t)strlen(s);\n    // Worst-case scenario (possible to optimize with dry run of parse)\n    // at least 2 characters per token in \"a b c d e\" plush null at the end:\n    const int32_t k = ((len + 2) / 2 + 1) * (int32_t)sizeof(void*) + (int32_t)sizeof(void*);\n    const int32_t n = k + (len + 2) * (int32_t)sizeof(char);\n    rt_fatal_if_error(rt_heap.allocate(null, &rt_args_memory, n, true));\n    rt_args.c = 0;\n    rt_args.v = (const char**)rt_args_memory;\n    char* d = (char*)(((char*)rt_args.v) + k);\n    char* e = d + n; // end of memory\n    // special rules for 1st argument:\n    if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n    if (*s == quote) {\n        s++;\n        while (*s != 0x00 && *s != quote && d < e) { *d++ = *s++; }\n        if (*s == quote) { // // closing quote\n            s++; // skip closing quote\n            *d++ = 0x00;\n        } else {\n            while (*s != 0x00) { s++; }\n        }\n    } else {\n        while (*s != 0x00 && *s != space && *s != tab && d < e) {\n            *d++ = *s++;\n        }\n    }\n    if (d < e) { *d++ = 0; }\n    while (d < e) {\n        while (*s == space || *s == tab) { s++; }\n        if (*s == 0) { break; }\n        if (*s == quote && s[1] == 0 && d < e) { // unbalanced single quote\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; } // spec does not say what to do\n            *d++ = *s++;\n        } else if (*s == quote) { // quoted arg\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n            rt_args_pair_t p = rt_args_parse_quoted(\n                    (rt_args_pair_t){ .s = s, .d = d, .e = e });\n            s = p.s; d = p.d;\n        } else { // non-quoted arg (that can have quoted strings inside)\n            if (rt_args.c < n) { rt_args.v[rt_args.c++] = d; }\n            while (*s != 0) {\n                if (*s == backslash) {\n                    rt_args_pair_t p = rt_args_parse_backslashes(\n                            (rt_args_pair_t){ .s = s, .d = d, .e = e });\n                    s = p.s; d = p.d;\n                } else if (*s == quote) {\n                    rt_args_pair_t p = rt_args_parse_quoted(\n                            (rt_args_pair_t){ .s = s, .d = d, .e = e });\n                    s = p.s; d = p.d;\n                } else if (*s == tab || *s == space) {\n                    break;\n                } else if (d < e) {\n                    *d++ = *s++;\n                }\n            }\n        }\n        if (d < e) { *d++ = 0; }\n    }\n    if (rt_args.c < n) {\n        rt_args.v[rt_args.c] = null;\n    }\n    rt_swear(rt_args.c < n, \"not enough memory - adjust guestimates\");\n    rt_swear(d <= e, \"not enough memory - adjust guestimates\");\n}\n\nstatic const char* rt_args_basename(void) {\n    static char basename[260];\n    rt_swear(rt_args.c > 0);\n    if (basename[0] == 0) {\n        const char* s = rt_args.v[0];\n        const char* b = s;\n        while (*s != 0) {\n            if (*s == '\\\\' || *s == '/') { b = s + 1; }\n            s++;\n        }\n        int32_t n = rt_str.len(b);\n        rt_swear(n < rt_countof(basename));\n        strncpy(basename, b, rt_countof(basename) - 1);\n        char* d = basename + n - 1;\n        while (d > basename && *d != '.') { d--; }\n        if (*d == '.') { *d = 0x00; }\n    }\n    return basename;\n}\n\nstatic void rt_args_fini(void) {\n    rt_heap.deallocate(null, rt_args_memory); // can be null is parse() was not called\n    rt_args_memory = null;\n    rt_args.c = 0;\n    rt_args.v = null;\n}\n\nstatic void rt_args_WinMain(void) {\n    rt_swear(rt_args.c == 0 && rt_args.v == null && rt_args.env == null);\n    rt_swear(rt_args_memory == null);\n    const uint16_t* wcl = GetCommandLineW();\n    int32_t n = (int32_t)rt_str.len16(wcl);\n    char* cl = null;\n    rt_fatal_if_error(rt_heap.allocate(null, (void**)&cl, n * 2 + 1, false));\n    rt_str.utf16to8(cl, n * 2 + 1, wcl, -1);\n    rt_args_parse(cl);\n    rt_heap.deallocate(null, cl);\n    rt_args.env = (const char**)(void*)_environ;\n}\n\n#ifdef RT_TESTS\n\n// https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments\n// Command-line input       argv[1]     argv[2]\t    argv[3]\n// \"a b c\" d e\t            a b c       d           e\n// \"ab\\\"c\" \"\\\\\" d           ab\"c        \\           d\n// a\\\\\\b d\"e f\"g h          a\\\\\\b       de fg       h\n// a\\\\\\\"b c d               a\\\"b        c           d\n// a\\\\\\\\\"b c\" d e           a\\\\b c      d           e\n// a\"b\"\" c d                ab\" c d\n\n#ifndef __INTELLISENSE__ // confused data analysis\n\nstatic void rt_args_test_verify(const char* cl, int32_t expected, ...) {\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        rt_println(\"cl: `%s`\", cl);\n    }\n    int32_t argc = rt_args.c;\n    const char** argv = rt_args.v;\n    void* memory = rt_args_memory;\n    rt_args.c = 0;\n    rt_args.v = null;\n    rt_args_memory = null;\n    rt_args_parse(cl);\n    va_list va;\n    va_start(va, expected);\n    for (int32_t i = 0; i < expected; i++) {\n        const char* s = va_arg(va, const char*);\n//      if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//          rt_println(\"rt_args.v[%d]: `%s` expected: `%s`\", i, rt_args.v[i], s);\n//      }\n        // Warning 6385: reading data outside of array\n        const char* ai = _Pragma(\"warning(suppress:  6385)\")rt_args.v[i];\n        rt_swear(strcmp(ai, s) == 0, \"rt_args.v[%d]: `%s` expected: `%s`\",\n                 i, ai, s);\n    }\n    va_end(va);\n    rt_args.fini();\n    // restore command line arguments:\n    rt_args.c = argc;\n    rt_args.v = argv;\n    rt_args_memory = memory;\n}\n\n#endif // __INTELLISENSE__\n\nstatic void rt_args_test(void) {\n    // The first argument (rt_args.v[0]) is treated specially.\n    // It represents the program name. Because it must be a valid pathname,\n    // parts surrounded by quote (\") are allowed. The quote aren't included\n    // in the rt_args.v[0] output. The parts surrounded by quote prevent\n    // interpretation of a space or tab character as the end of the argument.\n    // The escaping rules don't apply.\n    rt_args_test_verify(\"\\\"c:\\\\foo\\\\bar\\\\snafu.exe\\\"\", 1,\n                     \"c:\\\\foo\\\\bar\\\\snafu.exe\");\n    rt_args_test_verify(\"c:\\\\foo\\\\bar\\\\snafu.exe\", 1,\n                     \"c:\\\\foo\\\\bar\\\\snafu.exe\");\n    rt_args_test_verify(\"foo.exe \\\"a b c\\\" d e\", 4,\n                     \"foo.exe\", \"a b c\", \"d\", \"e\");\n    rt_args_test_verify(\"foo.exe \\\"ab\\\\\\\"c\\\" \\\"\\\\\\\\\\\" d\", 4,\n                     \"foo.exe\", \"ab\\\"c\", \"\\\\\", \"d\");\n    rt_args_test_verify(\"foo.exe a\\\\\\\\\\\\b d\\\"e f\\\"g h\", 4,\n                     \"foo.exe\", \"a\\\\\\\\\\\\b\", \"de fg\", \"h\");\n    rt_args_test_verify(\"foo.exe a\\\\\\\\\\\\b d\\\"e f\\\"g h\", 4,\n                     \"foo.exe\", \"a\\\\\\\\\\\\b\", \"de fg\", \"h\");\n    rt_args_test_verify(\"foo.exe a\\\"b\\\"\\\" c d\", 2, // unmatched quote\n                     \"foo.exe\", \"ab\\\" c d\");\n    // unbalanced quote and backslash:\n    rt_args_test_verify(\"foo.exe \\\"\",     2, \"foo.exe\", \"\\\"\");\n    rt_args_test_verify(\"foo.exe \\\\\",     2, \"foo.exe\", \"\\\\\");\n    rt_args_test_verify(\"foo.exe \\\\\\\\\",   2, \"foo.exe\", \"\\\\\\\\\");\n    rt_args_test_verify(\"foo.exe \\\\\\\\\\\\\", 2, \"foo.exe\", \"\\\\\\\\\\\\\");\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_args_test(void) {}\n\n#endif\n\nrt_args_if rt_args = {\n    .main         = rt_args_main,\n    .WinMain      = rt_args_WinMain,\n    .option_index = rt_args_option_index,\n    .remove_at    = rt_args_remove_at,\n    .option_bool  = rt_args_option_bool,\n    .option_int   = rt_args_option_int,\n    .option_str   = rt_args_option_str,\n    .basename     = rt_args_basename,\n    .fini         = rt_args_fini,\n    .test         = rt_args_test\n};\n"
  },
  {
    "path": "src/rt/rt_atomics.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n#include <stdatomic.h> // needs cl.exe /experimental:c11atomics command line\n\n// see: https://developercommunity.visualstudio.com/t/C11--C17-include-stdatomich-issue/10620622\n\n#pragma warning(push)\n#pragma warning(disable: 4746) // volatile access of 'int32_var' is subject to /volatile:<iso|ms> setting; consider using __iso_volatile_load/store intrinsic functions\n\n#ifndef UT_ATOMICS_HAS_STDATOMIC_H\n\nstatic int32_t rt_atomics_increment_int32(volatile int32_t* a) {\n    return InterlockedIncrement((volatile LONG*)a);\n}\n\nstatic int32_t rt_atomics_decrement_int32(volatile int32_t* a) {\n    return InterlockedDecrement((volatile LONG*)a);\n}\n\nstatic int64_t rt_atomics_increment_int64(volatile int64_t* a) {\n    return InterlockedIncrement64((__int64 volatile *)a);\n}\n\nstatic int64_t rt_atomics_decrement_int64(volatile int64_t* a) {\n    return InterlockedDecrement64((__int64 volatile *)a);\n}\n\nstatic int32_t rt_atomics_add_int32(volatile int32_t* a, int32_t v) {\n    return InterlockedAdd((LONG volatile *)a, v);\n}\n\nstatic int64_t rt_atomics_add_int64(volatile int64_t* a, int64_t v) {\n    return InterlockedAdd64((__int64 volatile *)a, v);\n}\n\nstatic int64_t rt_atomics_exchange_int64(volatile int64_t* a, int64_t v) {\n    return (int64_t)InterlockedExchange64((LONGLONG*)a, (LONGLONG)v);\n}\n\nstatic int32_t rt_atomics_exchange_int32(volatile int32_t* a, int32_t v) {\n    rt_assert(sizeof(int32_t) == sizeof(unsigned long));\n    return (int32_t)InterlockedExchange((volatile LONG*)a, (unsigned long)v);\n}\n\nstatic bool rt_atomics_compare_exchange_int64(volatile int64_t* a,\n        int64_t comparand, int64_t v) {\n    return (int64_t)InterlockedCompareExchange64((LONGLONG*)a,\n        (LONGLONG)v, (LONGLONG)comparand) == comparand;\n}\n\nstatic bool rt_atomics_compare_exchange_int32(volatile int32_t* a,\n        int32_t comparand, int32_t v) {\n    return (int64_t)InterlockedCompareExchange((LONG*)a,\n        (LONG)v, (LONG)comparand) == comparand;\n}\n\nstatic void memory_fence(void) {\n#ifdef _M_ARM64\natomic_thread_fence(memory_order_seq_cst);\n#else\n_mm_mfence();\n#endif\n}\n\n#else\n\n// stdatomic.h version:\n\n#ifndef __INTELLISENSE__ // IntelliSense chokes on _Atomic(_Type)\n// __INTELLISENSE__ Defined as 1 during an IntelliSense compiler pass\n// in the Visual Studio IDE. Otherwise, undefined. You can use this macro\n// to guard code the IntelliSense compiler doesn't understand,\n// or use it to toggle between the build and IntelliSense compiler.\n\n\n// _strong() operations are the same as _explicit(..., memory_order_seq_cst)\n// memory_order_seq_cst stands for Sequentially Consistent Ordering\n//\n// This is the strongest memory order, providing the guarantee that\n// all sequentially consistent operations appear to be executed in\n// the same order on all threads (cores)\n//\n// int_fast32_t: Fastest integer type with at least 32 bits.\n// int_least32_t: Smallest integer type with at least 32 bits.\n\nrt_static_assertion(sizeof(int32_t) == sizeof(int_fast32_t));\nrt_static_assertion(sizeof(int32_t) == sizeof(int_least32_t));\n\nstatic int32_t rt_atomics_increment_int32(volatile int32_t* a) {\n    return atomic_fetch_add((volatile atomic_int_fast32_t*)a, 1) + 1;\n}\n\nstatic int32_t rt_atomics_decrement_int32(volatile int32_t* a) {\n    return atomic_fetch_sub((volatile atomic_int_fast32_t*)a, 1) - 1;\n}\n\nstatic int64_t rt_atomics_increment_int64(volatile int64_t* a) {\n    return atomic_fetch_add((volatile atomic_int_fast64_t*)a, 1) + 1;\n}\n\nstatic int64_t rt_atomics_decrement_int64(volatile int64_t* a) {\n    return atomic_fetch_sub((volatile atomic_int_fast64_t*)a, 1) - 1;\n}\n\nstatic int32_t rt_atomics_add_int32(volatile int32_t* a, int32_t v) {\n    return atomic_fetch_add((volatile atomic_int_fast32_t*)a, v) + v;\n}\n\nstatic int64_t rt_atomics_add_int64(volatile int64_t* a, int64_t v) {\n    return atomic_fetch_add((volatile atomic_int_fast64_t*)a, v) + v;\n}\n\nstatic int64_t rt_atomics_exchange_int64(volatile int64_t* a, int64_t v) {\n    return atomic_exchange((volatile atomic_int_fast64_t*)a, v);\n}\n\nstatic int32_t rt_atomics_exchange_int32(volatile int32_t* a, int32_t v) {\n    return atomic_exchange((volatile atomic_int_fast32_t*)a, v);\n}\n\nstatic bool rt_atomics_compare_exchange_int64(volatile int64_t* a,\n    int64_t comparand, int64_t v) {\n    return atomic_compare_exchange_strong((volatile atomic_int_fast64_t*)a,\n        &comparand, v);\n}\n\n// Code here is not \"seen\" by IntelliSense but is compiled normally.\nstatic bool rt_atomics_compare_exchange_int32(volatile int32_t* a,\n    int32_t comparand, int32_t v) {\n    return atomic_compare_exchange_strong((volatile atomic_int_fast32_t*)a,\n        &comparand, v);\n}\n\nstatic void memory_fence(void) { atomic_thread_fence(memory_order_seq_cst); }\n\n#endif // __INTELLISENSE__\n\n#endif // UT_ATOMICS_HAS_STDATOMIC_H\n\nstatic int32_t rt_atomics_load_int32(volatile int32_t* a) {\n    return rt_atomics.add_int32(a, 0);\n}\n\nstatic int64_t rt_atomics_load_int64(volatile int64_t* a) {\n    return rt_atomics.add_int64(a, 0);\n}\n\nstatic void* rt_atomics_exchange_ptr(volatile void* *a, void* v) {\n    rt_static_assertion(sizeof(void*) == sizeof(uint64_t));\n    return (void*)(intptr_t)rt_atomics.exchange_int64((int64_t*)a, (int64_t)v);\n}\n\nstatic bool rt_atomics_compare_exchange_ptr(volatile void* *a, void* comparand, void* v) {\n    rt_static_assertion(sizeof(void*) == sizeof(int64_t));\n    return rt_atomics.compare_exchange_int64((int64_t*)a,\n        (int64_t)comparand, (int64_t)v);\n}\n\n#pragma push_macro(\"rt_sync_bool_compare_and_swap\")\n#pragma push_macro(\"rt_builtin_cpu_pause\")\n\n// https://en.wikipedia.org/wiki/Spinlock\n\n#define rt_sync_bool_compare_and_swap(p, old_val, new_val)          \\\n    (_InterlockedCompareExchange64(p, new_val, old_val) == old_val)\n\n// https://stackoverflow.com/questions/37063700/mm-pause-usage-in-gcc-on-intel\n#define rt_builtin_cpu_pause() do { YieldProcessor(); } while (0)\n\nstatic void spinlock_acquire(volatile int64_t* spinlock) {\n    // Very basic implementation of a spinlock. This is currently\n    // only used to guarantee thread-safety during context initialization\n    // and shutdown (which are both executed very infrequently and\n    // have minimal thread contention).\n    // Not a performance champion (because of mem_fence()) but serves\n    // the purpose. mem_fence() can be reduced to mem_sfence()... sigh\n    while (!rt_sync_bool_compare_and_swap(spinlock, 0, 1)) {\n        while (*spinlock) { rt_builtin_cpu_pause(); }\n    }\n    rt_atomics.memory_fence();\n    // not strictly necessary on strong mem model Intel/AMD but\n    // see: https://cfsamsonbooks.gitbook.io/explaining-atomics-in-rust/\n    //      Fig 2 Inconsistent C11 execution of SB and 2+2W\n    rt_assert(*spinlock == 1);\n}\n\n#pragma pop_macro(\"rt_builtin_cpu_pause\")\n#pragma pop_macro(\"rt_sync_bool_compare_and_swap\")\n\nstatic void spinlock_release(volatile int64_t* spinlock) {\n    rt_assert(*spinlock == 1);\n    *spinlock = 0;\n    // tribute to lengthy Linus discussion going since 2006:\n    rt_atomics.memory_fence();\n}\n\nstatic void rt_atomics_test(void) {\n    #ifdef RT_TESTS\n    volatile int32_t int32_var = 0;\n    volatile int64_t int64_var = 0;\n    volatile void* ptr_var = null;\n    int64_t spinlock = 0;\n    void* old_ptr = rt_atomics.exchange_ptr(&ptr_var, (void*)123);\n    rt_swear(old_ptr == null);\n    rt_swear(ptr_var == (void*)123);\n    int32_t incremented_int32 = rt_atomics.increment_int32(&int32_var);\n    rt_swear(incremented_int32 == 1);\n    rt_swear(int32_var == 1);\n    int32_t decremented_int32 = rt_atomics.decrement_int32(&int32_var);\n    rt_swear(decremented_int32 == 0);\n    rt_swear(int32_var == 0);\n    int64_t incremented_int64 = rt_atomics.increment_int64(&int64_var);\n    rt_swear(incremented_int64 == 1);\n    rt_swear(int64_var == 1);\n    int64_t decremented_int64 = rt_atomics.decrement_int64(&int64_var);\n    rt_swear(decremented_int64 == 0);\n    rt_swear(int64_var == 0);\n    int32_t added_int32 = rt_atomics.add_int32(&int32_var, 5);\n    rt_swear(added_int32 == 5);\n    rt_swear(int32_var == 5);\n    int64_t added_int64 = rt_atomics.add_int64(&int64_var, 10);\n    rt_swear(added_int64 == 10);\n    rt_swear(int64_var == 10);\n    int32_t old_int32 = rt_atomics.exchange_int32(&int32_var, 3);\n    rt_swear(old_int32 == 5);\n    rt_swear(int32_var == 3);\n    int64_t old_int64 = rt_atomics.exchange_int64(&int64_var, 6);\n    rt_swear(old_int64 == 10);\n    rt_swear(int64_var == 6);\n    bool int32_exchanged = rt_atomics.compare_exchange_int32(&int32_var, 3, 4);\n    rt_swear(int32_exchanged);\n    rt_swear(int32_var == 4);\n    bool int64_exchanged = rt_atomics.compare_exchange_int64(&int64_var, 6, 7);\n    rt_swear(int64_exchanged);\n    rt_swear(int64_var == 7);\n    ptr_var = (void*)0x123;\n    bool ptr_exchanged = rt_atomics.compare_exchange_ptr(&ptr_var,\n        (void*)0x123, (void*)0x456);\n    rt_swear(ptr_exchanged);\n    rt_swear(ptr_var == (void*)0x456);\n    rt_atomics.spinlock_acquire(&spinlock);\n    rt_swear(spinlock == 1);\n    rt_atomics.spinlock_release(&spinlock);\n    rt_swear(spinlock == 0);\n    int32_t loaded_int32 = rt_atomics.load32(&int32_var);\n    rt_swear(loaded_int32 == int32_var);\n    int64_t loaded_int64 = rt_atomics.load64(&int64_var);\n    rt_swear(loaded_int64 == int64_var);\n    rt_atomics.memory_fence();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\n#ifndef __INTELLISENSE__ // IntelliSense chokes on _Atomic(_Type)\n\nrt_static_assertion(sizeof(void*) == sizeof(int64_t));\nrt_static_assertion(sizeof(void*) == sizeof(uintptr_t));\n\nrt_atomics_if rt_atomics = {\n    .exchange_ptr    = rt_atomics_exchange_ptr,\n    .increment_int32 = rt_atomics_increment_int32,\n    .decrement_int32 = rt_atomics_decrement_int32,\n    .increment_int64 = rt_atomics_increment_int64,\n    .decrement_int64 = rt_atomics_decrement_int64,\n    .add_int32 = rt_atomics_add_int32,\n    .add_int64 = rt_atomics_add_int64,\n    .exchange_int32  = rt_atomics_exchange_int32,\n    .exchange_int64  = rt_atomics_exchange_int64,\n    .compare_exchange_int64 = rt_atomics_compare_exchange_int64,\n    .compare_exchange_int32 = rt_atomics_compare_exchange_int32,\n    .compare_exchange_ptr = rt_atomics_compare_exchange_ptr,\n    .load32 = rt_atomics_load_int32,\n    .load64 = rt_atomics_load_int64,\n    .spinlock_acquire = spinlock_acquire,\n    .spinlock_release = spinlock_release,\n    .memory_fence = memory_fence,\n    .test = rt_atomics_test\n};\n\n#endif // __INTELLISENSE__\n\n// 2024-03-20 latest windows runtime and toolchain cl.exe\n// ... VC\\Tools\\MSVC\\14.39.33519\\include\n// see:\n//     vcruntime_c11_atomic_support.h\n//     vcruntime_c11_stdatomic.h\n//     stdatomic.h\n// https://developercommunity.visualstudio.com/t/C11--C17-include--issue/10620622\n// cl.exe /std:c11 /experimental:c11atomics\n// command line option are required\n// even in C17 mode in spring of 2024\n\n#pragma warning(pop)\n"
  },
  {
    "path": "src/rt/rt_backtrace.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic void* rt_backtrace_process;\nstatic DWORD rt_backtrace_pid;\n\ntypedef rt_begin_packed struct symbol_info_s {\n    SYMBOL_INFO info; char name[rt_backtrace_max_symbol];\n} rt_end_packed symbol_info_t;\n\n#pragma push_macro(\"rt_backtrace_load_dll\")\n\n#define rt_backtrace_load_dll(fn) do {              \\\n    if (GetModuleHandleA(fn) == null) {      \\\n        rt_fatal_win32err(LoadLibraryA(fn)); \\\n    }                                        \\\n} while (0)\n\nstatic void rt_backtrace_init(void) {\n    if (rt_backtrace_process == null) {\n        rt_backtrace_load_dll(\"dbghelp.dll\");\n        rt_backtrace_load_dll(\"imagehlp.dll\");\n        DWORD options = SymGetOptions();\n//      options |= SYMOPT_DEBUG;\n        options |= SYMOPT_NO_PROMPTS;\n        options |= SYMOPT_LOAD_LINES;\n        options |= SYMOPT_UNDNAME;\n        options |= SYMOPT_LOAD_ANYTHING;\n        rt_swear(SymSetOptions(options));\n        rt_backtrace_pid = GetProcessId(GetCurrentProcess());\n        rt_swear(rt_backtrace_pid != 0);\n        rt_backtrace_process = OpenProcess(PROCESS_ALL_ACCESS, false,\n                                           rt_backtrace_pid);\n        rt_swear(rt_backtrace_process != null);\n        rt_swear(SymInitialize(rt_backtrace_process, null, true), \"%s\",\n                            rt_str.error(rt_core.err()));\n    }\n}\n\n#pragma pop_macro(\"rt_backtrace_load_dll\")\n\nstatic void rt_backtrace_capture(rt_backtrace_t* bt, int32_t skip) {\n    rt_backtrace_init();\n    SetLastError(0);\n    bt->frames = CaptureStackBackTrace(1 + skip, rt_countof(bt->stack),\n        bt->stack, (DWORD*)&bt->hash);\n    bt->error = rt_core.err();\n}\n\nstatic bool rt_backtrace_function(DWORD64 pc, SYMBOL_INFO* si) {\n    // find DLL exported function\n    bool found = false;\n    const DWORD64 module_base = SymGetModuleBase64(rt_backtrace_process, pc);\n    if (module_base != 0) {\n        const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT |\n                            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS;\n        HMODULE module_handle = null;\n        if (GetModuleHandleExA(flags, (const char*)pc, &module_handle)) {\n            DWORD bytes = 0;\n            IMAGE_EXPORT_DIRECTORY* dir = (IMAGE_EXPORT_DIRECTORY*)\n                    ImageDirectoryEntryToDataEx(module_handle, true,\n                            IMAGE_DIRECTORY_ENTRY_EXPORT, &bytes, null);\n            if (dir) {\n                uint8_t* m = (uint8_t*)module_handle;\n                DWORD* functions = (DWORD*)(m + dir->AddressOfFunctions);\n                DWORD* names = (DWORD*)(m + dir->AddressOfNames);\n                WORD* ordinals = (WORD*)(m + dir->AddressOfNameOrdinals);\n                DWORD64 address = 0; // closest address\n                DWORD64 min_distance = (DWORD64)-1;\n                const char* function = NULL; // closest function name\n                for (DWORD i = 0; i < dir->NumberOfNames; i++) {\n                    // function address\n                    DWORD64 fa = (DWORD64)(m + functions[ordinals[i]]);\n                    if (fa <= pc) {\n                        DWORD64 distance = pc - fa;\n                        if (distance < min_distance) {\n                            min_distance = distance;\n                            address = fa;\n                            function = (const char*)(m + names[i]);\n                        }\n                    }\n                }\n                if (function != null) {\n                    si->ModBase = (uint64_t)m;\n                    snprintf(si->Name, si->MaxNameLen - 1, \"%s\", function);\n                    si->Name[si->MaxNameLen - 1] = 0x00;\n                    si->NameLen = (DWORD)strlen(si->Name);\n                    si->Address = address;\n                    found = true;\n                }\n            }\n        }\n    }\n    return found;\n}\n\n// SimpleStackWalker::showVariablesAt() can be implemented if needed like this:\n// https://accu.org/journals/overload/29/165/orr/\n// https://github.com/rogerorr/articles/tree/main/Debugging_Optimised_Code\n// https://github.com/rogerorr/articles/blob/main/Debugging_Optimised_Code/SimpleStackWalker.cpp#L301\n\nstatic const void rt_backtrace_symbolize_inline_frame(rt_backtrace_t* bt,\n        int32_t i, DWORD64 pc, DWORD inline_context, symbol_info_t* si) {\n    si->info.Name[0] = 0;\n    si->info.NameLen = 0;\n    bt->file[i][0] = 0;\n    bt->line[i] = 0;\n    bt->symbol[i][0] = 0;\n    DWORD64 displacement = 0;\n    if (SymFromInlineContext(rt_backtrace_process, pc, inline_context,\n                            &displacement, &si->info)) {\n        rt_str_printf(bt->symbol[i], \"%s\", si->info.Name);\n    } else {\n        bt->error = rt_core.err();\n    }\n    IMAGEHLP_LINE64 li = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };\n    DWORD offset = 0;\n    if (SymGetLineFromInlineContext(rt_backtrace_process,\n                                    pc, inline_context, 0,\n                                    &offset, &li)) {\n        rt_str_printf(bt->file[i], \"%s\", li.FileName);\n        bt->line[i] = li.LineNumber;\n    }\n}\n\n// Too see kernel addresses in Stack Back Traces:\n//\n// Windows Registry Editor Version 5.00\n// [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management]\n// \"DisablePagingExecutive\"=dword:00000001\n//\n// https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc757875(v=ws.10)\n\nstatic int32_t rt_backtrace_symbolize_frame(rt_backtrace_t* bt, int32_t i) {\n    const DWORD64 pc = (DWORD64)bt->stack[i];\n    symbol_info_t si = {\n        .info = { .SizeOfStruct = sizeof(SYMBOL_INFO),\n                  .MaxNameLen = rt_countof(si.name) }\n    };\n    bt->file[i][0] = 0;\n    bt->line[i] = 0;\n    bt->symbol[i][0] = 0;\n    DWORD64 offsetFromSymbol = 0;\n    const DWORD inline_count =\n        SymAddrIncludeInlineTrace(rt_backtrace_process, pc);\n    if (inline_count > 0) {\n        DWORD ic = 0; // inline context\n        DWORD fi = 0; // frame index\n        if (SymQueryInlineTrace(rt_backtrace_process,\n                                pc, 0, pc, pc, &ic, &fi)) {\n            for (DWORD k = 0; k < inline_count; k++, ic++) {\n                rt_backtrace_symbolize_inline_frame(bt, i, pc, ic, &si);\n                i++;\n            }\n        }\n    } else {\n        if (SymFromAddr(rt_backtrace_process, pc, &offsetFromSymbol, &si.info)) {\n            rt_str_printf(bt->symbol[i], \"%s\", si.info.Name);\n            DWORD d = 0; // displacement\n            IMAGEHLP_LINE64 ln = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };\n            if (SymGetLineFromAddr64(rt_backtrace_process, pc, &d, &ln)) {\n                bt->line[i] = ln.LineNumber;\n                rt_str_printf(bt->file[i], \"%s\", ln.FileName);\n            } else {\n                bt->error = rt_core.err();\n                if (rt_backtrace_function(pc, &si.info)) {\n                    GetModuleFileNameA((HANDLE)si.info.ModBase, bt->file[i],\n                        rt_countof(bt->file[i]) - 1);\n                    bt->file[i][rt_countof(bt->file[i]) - 1] = 0;\n                    bt->line[i]    = 0;\n                } else  {\n                    bt->file[i][0] = 0x00;\n                    bt->line[i]    = 0;\n                }\n            }\n            i++;\n        } else {\n            bt->error = rt_core.err();\n            if (rt_backtrace_function(pc, &si.info)) {\n                rt_str_printf(bt->symbol[i], \"%s\", si.info.Name);\n                GetModuleFileNameA((HANDLE)si.info.ModBase, bt->file[i],\n                    rt_countof(bt->file[i]) - 1);\n                bt->file[i][rt_countof(bt->file[i]) - 1] = 0;\n                bt->error = 0;\n                i++;\n            } else {\n                // will not do i++\n            }\n        }\n    }\n    return i;\n}\n\nstatic void rt_backtrace_symbolize_backtrace(rt_backtrace_t* bt) {\n    rt_assert(!bt->symbolized);\n    bt->error = 0;\n    rt_backtrace_init();\n    // rt_backtrace_symbolize_frame() may produce zero, one or many frames\n    int32_t n = bt->frames;\n    void* stack[rt_countof(bt->stack)];\n    memcpy(stack, bt->stack, n * sizeof(stack[0]));\n    bt->frames = 0;\n    for (int32_t i = 0; i < n && bt->frames < rt_countof(bt->stack); i++) {\n        bt->stack[bt->frames] = stack[i];\n        bt->frames = rt_backtrace_symbolize_frame(bt, i);\n    }\n    bt->symbolized = true;\n}\n\nstatic void rt_backtrace_symbolize(rt_backtrace_t* bt) {\n    if (!bt->symbolized) { rt_backtrace_symbolize_backtrace(bt); }\n}\n\nstatic const char* rt_backtrace_stops[] = {\n    \"main\",\n    \"WinMain\",\n    \"BaseThreadInitThunk\",\n    \"RtlUserThreadStart\",\n    \"mainCRTStartup\",\n    \"WinMainCRTStartup\",\n    \"invoke_main\",\n    \"NdrInterfacePointerMemorySize\",\n    null\n};\n\nstatic void rt_backtrace_trace(const rt_backtrace_t* bt, const char* stop) {\n    #pragma push_macro(\"rt_backtrace_glyph_called_from\")\n    #define rt_backtrace_glyph_called_from rt_glyph_north_west_arrow_with_hook\n    rt_assert(bt->symbolized, \"need rt_backtrace.symbolize(bt)\");\n    const char** alt = stop != null && strcmp(stop, \"*\") == 0 ?\n                       rt_backtrace_stops : null;\n    for (int32_t i = 0; i < bt->frames; i++) {\n        rt_debug.println(bt->file[i], bt->line[i], bt->symbol[i],\n            rt_backtrace_glyph_called_from \"%s\",\n            i == i < bt->frames - 1 ? \"\\n\" : \"\"); // extra \\n for last line\n        if (stop != null && strcmp(bt->symbol[i], stop) == 0) { break; }\n        const char** s = alt;\n        while (s != null && *s != null && strcmp(bt->symbol[i], *s) != 0) { s++; }\n        if (s != null && *s != null)  { break; }\n    }\n    #pragma pop_macro(\"rt_backtrace_glyph_called_from\")\n}\n\n\nstatic const char* rt_backtrace_string(const rt_backtrace_t* bt,\n        char* text, int32_t count) {\n    rt_assert(bt->symbolized, \"need rt_backtrace.symbolize(bt)\");\n    char s[1024];\n    char* p = text;\n    int32_t n = count;\n    for (int32_t i = 0; i < bt->frames && n > 128; i++) {\n        int32_t line = bt->line[i];\n        const char* file = bt->file[i];\n        const char* name = bt->symbol[i];\n        if (file[0] != 0 && name[0] != 0) {\n            rt_str_printf(s, \"%s(%d): %s\\n\", file, line, name);\n        } else if (file[0] == 0 && name[0] != 0) {\n            rt_str_printf(s, \"%s\\n\", name);\n        }\n        s[rt_countof(s) - 1] = 0;\n        int32_t k = (int32_t)strlen(s);\n        if (k < n) {\n            memcpy(p, s, (size_t)k + 1);\n            p += k;\n            n -= k;\n        }\n    }\n    return text;\n}\n\ntypedef struct { char name[32]; } rt_backtrace_thread_name_t;\n\nstatic rt_backtrace_thread_name_t rt_backtrace_thread_name(HANDLE thread) {\n    rt_backtrace_thread_name_t tn;\n    tn.name[0] = 0;\n    wchar_t* thread_name = null;\n    if (SUCCEEDED(GetThreadDescription(thread, &thread_name))) {\n        rt_str.utf16to8(tn.name, rt_countof(tn.name), thread_name, -1);\n        LocalFree(thread_name);\n    }\n    return tn;\n}\n\nstatic void rt_backtrace_context(rt_thread_t thread, const void* ctx,\n        rt_backtrace_t* bt) {\n    CONTEXT* context = (CONTEXT*)ctx;\n    STACKFRAME64 stack_frame = { 0 };\n    int machine_type = IMAGE_FILE_MACHINE_UNKNOWN;\n    #if defined(_M_IX86)\n        #error \"Unsupported platform\"\n    #elif defined(_M_ARM64)\n        machine_type = IMAGE_FILE_MACHINE_ARM64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Pc, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Fp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Sp, .Mode = AddrModeFlat}\n        };\n    #elif defined(_M_X64)\n        machine_type = IMAGE_FILE_MACHINE_AMD64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Rip, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Rbp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Rsp, .Mode = AddrModeFlat}\n        };\n    #elif defined(_M_IA64)\n        int machine_type = IMAGE_FILE_MACHINE_IA64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC     = {.Offset = context->StIIP, .Mode = AddrModeFlat},\n            .AddrFrame  = {.Offset = context->IntSp, .Mode = AddrModeFlat},\n            .AddrBStore = {.Offset = context->RsBSP, .Mode = AddrModeFlat},\n            .AddrStack  = {.Offset = context->IntSp, .Mode = AddrModeFlat}\n        }\n    #elif defined(_M_ARM64)\n        machine_type = IMAGE_FILE_MACHINE_ARM64;\n        stack_frame = (STACKFRAME64){\n            .AddrPC    = {.Offset = context->Pc, .Mode = AddrModeFlat},\n            .AddrFrame = {.Offset = context->Fp, .Mode = AddrModeFlat},\n            .AddrStack = {.Offset = context->Sp, .Mode = AddrModeFlat}\n        };\n    #else\n        #error \"Unsupported platform\"\n    #endif\n    rt_backtrace_init();\n    while (StackWalk64(machine_type, rt_backtrace_process,\n            (HANDLE)thread, &stack_frame, context, null,\n            SymFunctionTableAccess64, SymGetModuleBase64, null)) {\n        DWORD64 pc = stack_frame.AddrPC.Offset;\n        if (pc == 0) { break; }\n        if (bt->frames < rt_countof(bt->stack)) {\n            bt->stack[bt->frames] = (void*)pc;\n            bt->frames = rt_backtrace_symbolize_frame(bt, bt->frames);\n        }\n    }\n    bt->symbolized = true;\n}\n\nstatic void rt_backtrace_thread(HANDLE thread, rt_backtrace_t* bt) {\n    bt->frames = 0;\n    // cannot suspend callers thread\n    rt_swear(rt_thread.id_of(thread) != rt_thread.id());\n    if (SuspendThread(thread) != (DWORD)-1) {\n        CONTEXT context = { .ContextFlags = CONTEXT_FULL };\n        GetThreadContext(thread, &context);\n        rt_backtrace.context(thread, &context, bt);\n        if (ResumeThread(thread) == (DWORD)-1) {\n            rt_println(\"ResumeThread() failed %s\", rt_str.error(rt_core.err()));\n            ExitProcess(0xBD);\n        }\n    }\n}\n\nstatic void rt_backtrace_trace_self(const char* stop) {\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.capture(&bt, 2);\n    rt_backtrace.symbolize(&bt);\n    rt_backtrace.trace(&bt, stop);\n}\n\nstatic void rt_backtrace_trace_all_but_self(void) {\n    rt_backtrace_init();\n    rt_assert(rt_backtrace_process != null && rt_backtrace_pid != 0);\n    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);\n    if (snapshot == INVALID_HANDLE_VALUE) {\n        rt_println(\"CreateToolhelp32Snapshot failed %s\",\n                rt_str.error(rt_core.err()));\n    } else {\n        THREADENTRY32 te = { .dwSize = sizeof(THREADENTRY32) };\n        if (!Thread32First(snapshot, &te)) {\n            rt_println(\"Thread32First failed %s\", rt_str.error(rt_core.err()));\n        } else {\n            do {\n                if (te.th32OwnerProcessID == rt_backtrace_pid) {\n                    static const DWORD flags = THREAD_ALL_ACCESS |\n                       THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT;\n                    uint32_t tid = te.th32ThreadID;\n                    if (tid != (uint32_t)rt_thread.id()) {\n                        HANDLE thread = OpenThread(flags, false, tid);\n                        if (thread != null) {\n                            rt_backtrace_t bt = {0};\n                            rt_backtrace_thread(thread, &bt);\n                            rt_backtrace_thread_name_t tn = rt_backtrace_thread_name(thread);\n                            rt_debug.println(\">Thread\", tid, tn.name,\n                                \"id 0x%08X (%d)\", tid, tid);\n                            if (bt.frames > 0) {\n                                rt_backtrace.trace(&bt, \"*\");\n                            }\n                            rt_debug.println(\"<Thread\", tid, tn.name, \"\");\n                            rt_win32_close_handle(thread);\n                        }\n                    }\n                }\n            } while (Thread32Next(snapshot, &te));\n        }\n        rt_win32_close_handle(snapshot);\n    }\n}\n\n#ifdef RT_TESTS\n\nstatic bool (*rt_backtrace_debug_tee)(const char* s, int32_t count);\n\nstatic char  rt_backtrace_test_output[16 * 1024];\nstatic char* rt_backtrace_test_output_p;\n\nstatic bool rt_backtrace_tee(const char* s, int32_t count) {\n    if (count > 0 && s[count - 1] == 0) { // zero terminated\n        int32_t k = (int32_t)(uintptr_t)(\n            rt_backtrace_test_output_p - rt_backtrace_test_output);\n        int32_t space = rt_countof(rt_backtrace_test_output) - k;\n        if (count < space) {\n            memcpy(rt_backtrace_test_output_p, s, count);\n            rt_backtrace_test_output_p += count - 1; // w/o 0x00\n        }\n    } else {\n        rt_debug.breakpoint(); // incorrect output() cannot append\n    }\n    return true; // intercepted, do not do OutputDebugString()\n}\n\nstatic void rt_backtrace_test_thread(void* e) {\n    rt_event.wait(*(rt_event_t*)e);\n}\n\nstatic void rt_backtrace_test(void) {\n    rt_backtrace_debug_tee = rt_debug.tee;\n    rt_backtrace_test_output_p = rt_backtrace_test_output;\n    rt_backtrace_test_output[0] = 0x00;\n    rt_debug.tee = rt_backtrace_tee;\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.capture(&bt, 0);\n    // rt_backtrace_test <- rt_core_test <- run <- main\n    rt_swear(bt.frames >= 3);\n    rt_backtrace.symbolize(&bt);\n    rt_backtrace.trace(&bt, null);\n    rt_backtrace.trace(&bt, \"main\");\n    rt_backtrace.trace(&bt, null);\n    rt_backtrace.trace(&bt, \"main\");\n    rt_event_t e = rt_event.create();\n    rt_thread_t thread = rt_thread.start(rt_backtrace_test_thread, &e);\n    rt_backtrace.trace_all_but_self();\n    rt_event.set(e);\n    rt_thread.join(thread, -1.0);\n    rt_event.dispose(e);\n    rt_debug.tee = rt_backtrace_debug_tee;\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        rt_debug.output(rt_backtrace_test_output,\n            (int32_t)strlen(rt_backtrace_test_output) + 1);\n    }\n    rt_swear(strstr(rt_backtrace_test_output, \"rt_backtrace_test\") != null,\n          \"%s\", rt_backtrace_test_output);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_backtrace_test(void) { }\n\n#endif\n\nrt_backtrace_if rt_backtrace = {\n    .capture            = rt_backtrace_capture,\n    .context            = rt_backtrace_context,\n    .symbolize          = rt_backtrace_symbolize,\n    .trace              = rt_backtrace_trace,\n    .trace_self         = rt_backtrace_trace_self,\n    .trace_all_but_self = rt_backtrace_trace_all_but_self,\n    .string             = rt_backtrace_string,\n    .test               = rt_backtrace_test\n};\n"
  },
  {
    "path": "src/rt/rt_clipboard.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic errno_t rt_clipboard_put_text(const char* utf8) {\n    int32_t chars = rt_str.utf16_chars(utf8, -1);\n    int32_t bytes = (chars + 1) * 2;\n    uint16_t* utf16 = null;\n    errno_t r = rt_heap.alloc((void**)&utf16, (size_t)bytes);\n    if (utf16 != null) {\n        rt_str.utf8to16(utf16, bytes, utf8, -1);\n        rt_assert(utf16[chars - 1] == 0);\n        const int32_t n = (int32_t)rt_str.len16(utf16) + 1;\n        r = OpenClipboard(GetDesktopWindow()) ? 0 : rt_core.err();\n        if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n        if (r == 0) {\n            r = EmptyClipboard() ? 0 : rt_core.err();\n            if (r != 0) { rt_println(\"EmptyClipboard() failed %s\", rt_strerr(r)); }\n        }\n        void* global = null;\n        if (r == 0) {\n            global = GlobalAlloc(GMEM_MOVEABLE, (size_t)n * 2);\n            r = global != null ? 0 : rt_core.err();\n            if (r != 0) { rt_println(\"GlobalAlloc() failed %s\", rt_strerr(r)); }\n        }\n        if (r == 0) {\n            char* d = (char*)GlobalLock(global);\n            rt_not_null(d);\n            memcpy(d, utf16, (size_t)n * 2);\n            r = rt_b2e(SetClipboardData(CF_UNICODETEXT, global));\n            GlobalUnlock(global);\n            if (r != 0) {\n                rt_println(\"SetClipboardData() failed %s\", rt_strerr(r));\n                GlobalFree(global);\n            } else {\n                // do not free global memory. It's owned by system clipboard now\n            }\n        }\n        if (r == 0) {\n            r = rt_b2e(CloseClipboard());\n            if (r != 0) {\n                rt_println(\"CloseClipboard() failed %s\", rt_strerr(r));\n            }\n        }\n        rt_heap.free(utf16);\n    }\n    return r;\n}\n\nstatic errno_t rt_clipboard_get_text(char* utf8, int32_t* bytes) {\n    rt_not_null(bytes);\n    errno_t r = rt_b2e(OpenClipboard(GetDesktopWindow()));\n    if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n    if (r == 0) {\n        HANDLE global = GetClipboardData(CF_UNICODETEXT);\n        if (global == null) {\n            r = rt_core.err();\n        } else {\n            uint16_t* utf16 = (uint16_t*)GlobalLock(global);\n            if (utf16 != null) {\n                int32_t utf8_bytes = rt_str.utf8_bytes(utf16, -1);\n                if (utf8 != null) {\n                    char* decoded = (char*)malloc((size_t)utf8_bytes);\n                    if (decoded == null) {\n                        r = ERROR_OUTOFMEMORY;\n                    } else {\n                        rt_str.utf16to8(decoded, utf8_bytes, utf16, -1);\n                        int32_t n = rt_min(*bytes, utf8_bytes);\n                        memcpy(utf8, decoded, (size_t)n);\n                        free(decoded);\n                        if (n < utf8_bytes) {\n                            r = ERROR_INSUFFICIENT_BUFFER;\n                        }\n                    }\n                }\n                *bytes = utf8_bytes;\n                GlobalUnlock(global);\n            }\n        }\n        r = rt_b2e(CloseClipboard());\n    }\n    return r;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_clipboard_test(void) {\n    rt_fatal_if_error(rt_clipboard.put_text(\"Hello Clipboard\"));\n    char text[256];\n    int32_t bytes = rt_countof(text);\n    rt_fatal_if_error(rt_clipboard.get_text(text, &bytes));\n    rt_swear(strcmp(text, \"Hello Clipboard\") == 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_clipboard_test(void) {\n}\n\n#endif\n\nrt_clipboard_if rt_clipboard = {\n    .put_text   = rt_clipboard_put_text,\n    .get_text   = rt_clipboard_get_text,\n    .put_image  = null, // implemented in ui.app\n    .test       = rt_clipboard_test\n};\n"
  },
  {
    "path": "src/rt/rt_clock.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nenum {\n    rt_clock_nsec_in_usec = 1000, // nano in micro\n    rt_clock_nsec_in_msec = rt_clock_nsec_in_usec * 1000, // nano in milli\n    rt_clock_nsec_in_sec  = rt_clock_nsec_in_msec * 1000,\n    rt_clock_usec_in_msec = 1000, // micro in mill\n    rt_clock_msec_in_sec  = 1000, // milli in sec\n    rt_clock_usec_in_sec  = rt_clock_usec_in_msec * rt_clock_msec_in_sec // micro in sec\n};\n\nstatic uint64_t rt_clock_microseconds_since_epoch(void) { // NOT monotonic\n    FILETIME ft; // time in 100ns interval (tenth of microsecond)\n    // since 12:00 A.M. January 1, 1601 Coordinated Universal Time (UTC)\n    GetSystemTimePreciseAsFileTime(&ft);\n    uint64_t microseconds =\n        (((uint64_t)ft.dwHighDateTime) << 32 | ft.dwLowDateTime) / 10;\n    rt_assert(microseconds > 0);\n    return microseconds;\n}\n\nstatic uint64_t rt_clock_localtime(void) {\n    TIME_ZONE_INFORMATION tzi; // UTC = local time + bias\n    GetTimeZoneInformation(&tzi);\n    uint64_t bias = (uint64_t)tzi.Bias * 60LL * 1000 * 1000; // in microseconds\n    return rt_clock_microseconds_since_epoch() - bias;\n}\n\nstatic void rt_clock_utc(uint64_t microseconds,\n        int32_t* year, int32_t* month, int32_t* day,\n        int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms, int32_t* mc) {\n    uint64_t time_in_100ns = microseconds * 10;\n    FILETIME mst = { (DWORD)(time_in_100ns & 0xFFFFFFFF),\n                     (DWORD)(time_in_100ns >> 32) };\n    SYSTEMTIME utc;\n    FileTimeToSystemTime(&mst, &utc);\n    *year = utc.wYear;\n    *month = utc.wMonth;\n    *day = utc.wDay;\n    *hh = utc.wHour;\n    *mm = utc.wMinute;\n    *ss = utc.wSecond;\n    *ms = utc.wMilliseconds;\n    *mc = microseconds % 1000;\n}\n\nstatic void rt_clock_local(uint64_t microseconds,\n        int32_t* year, int32_t* month, int32_t* day,\n        int32_t* hh, int32_t* mm, int32_t* ss, int32_t* ms, int32_t* mc) {\n    uint64_t time_in_100ns = microseconds * 10;\n    FILETIME mst = { (DWORD)(time_in_100ns & 0xFFFFFFFF), (DWORD)(time_in_100ns >> 32) };\n    SYSTEMTIME utc;\n    FileTimeToSystemTime(&mst, &utc);\n    DYNAMIC_TIME_ZONE_INFORMATION tzi;\n    GetDynamicTimeZoneInformation(&tzi);\n    SYSTEMTIME lt = {0};\n    SystemTimeToTzSpecificLocalTimeEx(&tzi, &utc, &lt);\n    *year = lt.wYear;\n    *month = lt.wMonth;\n    *day = lt.wDay;\n    *hh = lt.wHour;\n    *mm = lt.wMinute;\n    *ss = lt.wSecond;\n    *ms = lt.wMilliseconds;\n    *mc = microseconds % 1000;\n}\n\nstatic fp64_t rt_clock_seconds(void) { // since_boot\n    LARGE_INTEGER qpc;\n    QueryPerformanceCounter(&qpc);\n    static fp64_t one_over_freq;\n    if (one_over_freq == 0) {\n        LARGE_INTEGER frequency;\n        QueryPerformanceFrequency(&frequency);\n        one_over_freq = 1.0 / (fp64_t)frequency.QuadPart;\n    }\n    return (fp64_t)qpc.QuadPart * one_over_freq;\n}\n\n// Max duration in nanoseconds=2^64 - 1 nanoseconds\n//                          2^64 - 1 ns        1 sec          1 min\n// Max Duration in Hours =  ----------- x  ------------ x -------------\n//                          10^9 ns / s    60 sec / min   60 min / hour\n//\n//                              1 hour\n// Max Duration in Days =  ---------------\n//                          24 hours / day\n//\n// it would take approximately 213,503 days (or about 584.5 years)\n// for rt_clock.nanoseconds() to overflow\n//\n// for divider = rt_num.gcd32(nsec_in_sec, freq) below and 10MHz timer\n// the actual duration is shorter because of (mul == 100)\n//    (uint64_t)qpc.QuadPart * mul\n// 64 bit overflow and is about 5.8 years.\n//\n// In a long running code like services is advisable to use\n// rt_clock.nanoseconds() to measure only deltas and pay close attention\n// to the wrap around despite of 5 years monotony\n\nstatic uint64_t rt_clock_nanoseconds(void) {\n    LARGE_INTEGER qpc;\n    QueryPerformanceCounter(&qpc);\n    static uint32_t freq;\n    static uint32_t mul = rt_clock_nsec_in_sec;\n    if (freq == 0) {\n        LARGE_INTEGER frequency;\n        QueryPerformanceFrequency(&frequency);\n        rt_assert(frequency.HighPart == 0);\n        // even 1GHz frequency should fit into 32 bit unsigned\n        rt_assert(frequency.HighPart == 0, \"%08lX%%08lX\",\n               frequency.HighPart, frequency.LowPart);\n        // known values: 10,000,000 and 3,000,000 10MHz, 3MHz\n        rt_assert(frequency.LowPart % (1000 * 1000) == 0);\n        // if we start getting weird frequencies not\n        // multiples of MHz rt_num.gcd() approach may need\n        // to be revised in favor of rt_num.muldiv64x64()\n        freq = frequency.LowPart;\n        rt_assert(freq != 0 && freq < (uint32_t)rt_clock.nsec_in_sec);\n        // to avoid rt_num.muldiv128:\n        uint32_t divider = rt_num.gcd32((uint32_t)rt_clock.nsec_in_sec, freq);\n        freq /= divider;\n        mul  /= divider;\n    }\n    uint64_t ns_mul_freq = (uint64_t)qpc.QuadPart * mul;\n    return freq == 1 ? ns_mul_freq : ns_mul_freq / freq;\n}\n\n// Difference between 1601 and 1970 in microseconds:\n\nstatic const uint64_t rt_clock_epoch_diff_usec = 11644473600000000ULL;\n\nstatic uint64_t rt_clock_unix_microseconds(void) {\n    return rt_clock.microseconds() - rt_clock_epoch_diff_usec;\n}\n\nstatic uint64_t rt_clock_unix_seconds(void) {\n    return rt_clock.unix_microseconds() / (uint64_t)rt_clock.usec_in_sec;\n}\n\nstatic void rt_clock_test(void) {\n    #ifdef RT_TESTS\n    // TODO: implement more tests\n    uint64_t t0 = rt_clock.nanoseconds();\n    uint64_t t1 = rt_clock.nanoseconds();\n    int32_t count = 0;\n    while (t0 == t1 && count < 1024) {\n        t1 = rt_clock.nanoseconds();\n        count++;\n    }\n    rt_swear(t0 != t1, \"count: %d t0: %lld t1: %lld\", count, t0, t1);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_clock_if rt_clock = {\n    .nsec_in_usec      = rt_clock_nsec_in_usec,\n    .nsec_in_msec      = rt_clock_nsec_in_msec,\n    .nsec_in_sec       = rt_clock_nsec_in_sec,\n    .usec_in_msec      = rt_clock_usec_in_msec,\n    .msec_in_sec       = rt_clock_msec_in_sec,\n    .usec_in_sec       = rt_clock_usec_in_sec,\n    .seconds           = rt_clock_seconds,\n    .nanoseconds       = rt_clock_nanoseconds,\n    .unix_microseconds = rt_clock_unix_microseconds,\n    .unix_seconds      = rt_clock_unix_seconds,\n    .microseconds      = rt_clock_microseconds_since_epoch,\n    .localtime         = rt_clock_localtime,\n    .utc               = rt_clock_utc,\n    .local             = rt_clock_local,\n    .test              = rt_clock_test\n};\n"
  },
  {
    "path": "src/rt/rt_config.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// On Unix the implementation should keep KV pairs in\n// key-named files inside .name/ folder\n\nstatic const char* rt_config_apps = \"Software\\\\ui\\\\apps\";\n\nstatic const DWORD rt_config_access =\n    KEY_READ|KEY_WRITE|KEY_SET_VALUE|KEY_QUERY_VALUE|\n    KEY_ENUMERATE_SUB_KEYS|DELETE;\n\nstatic errno_t rt_config_get_reg_key(const char* name, HKEY *key) {\n    char path[256] = {0};\n    rt_str_printf(path, \"%s\\\\%s\", rt_config_apps, name);\n    errno_t r = RegOpenKeyExA(HKEY_CURRENT_USER, path, 0, rt_config_access, key);\n    if (r != 0) {\n        const DWORD option = REG_OPTION_NON_VOLATILE;\n        r = RegCreateKeyExA(HKEY_CURRENT_USER, path, 0, null, option,\n                            rt_config_access, null, key, null);\n    }\n    return r;\n}\n\nstatic errno_t rt_config_save(const char* name,\n        const char* key, const void* data, int32_t bytes) {\n    errno_t r = 0;\n    HKEY k = null;\n    r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        r = RegSetValueExA(k, key, 0, REG_BINARY,\n            (const uint8_t*)data, (DWORD)bytes);\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic errno_t rt_config_remove(const char* name, const char* key) {\n    errno_t r = 0;\n    HKEY k = null;\n    r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        r = RegDeleteValueA(k, key);\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic errno_t rt_config_clean(const char* name) {\n    errno_t r = 0;\n    HKEY k = null;\n    if (RegOpenKeyExA(HKEY_CURRENT_USER, rt_config_apps,\n                                      0, rt_config_access, &k) == 0) {\n       r = RegDeleteTreeA(k, name);\n       rt_fatal_if_error(RegCloseKey(k));\n    }\n    return r;\n}\n\nstatic int32_t rt_config_size(const char* name, const char* key) {\n    int32_t bytes = -1;\n    HKEY k = null;\n    errno_t r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        DWORD type = REG_BINARY;\n        DWORD cb = 0;\n        r = RegQueryValueExA(k, key, null, &type, null, &cb);\n        if (r == ERROR_FILE_NOT_FOUND) {\n            bytes = 0; // do not report data_size() often used this way\n        } else if (r != 0) {\n            rt_println(\"%s.RegQueryValueExA(\\\"%s\\\") failed %s\",\n                name, key, rt_strerr(r));\n            bytes = 0; // on any error behave as empty data\n        } else {\n            bytes = (int32_t)cb;\n        }\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return bytes;\n}\n\nstatic int32_t rt_config_load(const char* name,\n        const char* key, void* data, int32_t bytes) {\n    int32_t read = -1;\n    HKEY k = null;\n    errno_t r = rt_config_get_reg_key(name, &k);\n    if (k != null) {\n        DWORD type = REG_BINARY;\n        DWORD cb = (DWORD)bytes;\n        r = RegQueryValueExA(k, key, null, &type, (uint8_t*)data, &cb);\n        if (r == ERROR_MORE_DATA) {\n            // returns -1 ui_app.data_size() should be used\n        } else if (r != 0) {\n            if (r != ERROR_FILE_NOT_FOUND) {\n                rt_println(\"%s.RegQueryValueExA(\\\"%s\\\") failed %s\",\n                    name, key, rt_strerr(r));\n            }\n            read = 0; // on any error behave as empty data\n        } else {\n            read = (int32_t)cb;\n        }\n        rt_fatal_if_error(RegCloseKey(k));\n    }\n    return read;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_config_test(void) {\n    const char* name = strrchr(rt_args.v[0], '\\\\');\n    if (name == null) { name = strrchr(rt_args.v[0], '/'); }\n    name = name != null ? name + 1 : rt_args.v[0];\n    rt_swear(name != null);\n    const char* key = \"test\";\n    const char data[] = \"data\";\n    int32_t bytes = sizeof(data);\n    rt_swear(rt_config.save(name, key, data, bytes) == 0);\n    char read[256];\n    rt_swear(rt_config.load(name, key, read, bytes) == bytes);\n    int32_t size = rt_config.size(name, key);\n    rt_swear(size == bytes);\n    rt_swear(rt_config.remove(name, key) == 0);\n    rt_swear(rt_config.clean(name) == 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_config_test(void) { }\n\n#endif\n\nrt_config_if rt_config = {\n    .save   = rt_config_save,\n    .size   = rt_config_size,\n    .load   = rt_config_load,\n    .remove = rt_config_remove,\n    .clean  = rt_config_clean,\n    .test   = rt_config_test\n};\n"
  },
  {
    "path": "src/rt/rt_core.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// abort does NOT call atexit() functions and\n// does NOT flush rt_streams. Also Win32 runtime\n// abort() attempt to show Abort/Retry/Ignore\n// MessageBox - thus ExitProcess()\n\nstatic void rt_core_abort(void) { ExitProcess(ERROR_FATAL_APP_EXIT); }\n\nstatic void rt_core_exit(int32_t exit_code) { exit(exit_code); }\n\n// TODO: consider r = HRESULT_FROM_WIN32() and r = HRESULT_CODE(hr);\n// this separates posix error codes from win32 error codes\n\n\nstatic errno_t rt_core_err(void) { return (errno_t)GetLastError(); }\n\nstatic void rt_core_seterr(errno_t err) { SetLastError((DWORD)err); }\n\nrt_static_init(runtime) {\n    SetErrorMode(\n        // The system does not display the critical-error-handler message box.\n        // Instead, the system sends the error to the calling process:\n        SEM_FAILCRITICALERRORS|\n        // The system automatically fixes memory alignment faults and\n        // makes them invisible to the application.\n        SEM_NOALIGNMENTFAULTEXCEPT|\n        // The system does not display the Windows Error Reporting dialog.\n        SEM_NOGPFAULTERRORBOX|\n        // The OpenFile function does not display a message box when it fails\n        // to find a file. Instead, the error is returned to the caller.\n        // This error mode overrides the OF_PROMPT flag.\n        SEM_NOOPENFILEERRORBOX);\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_core_test(void) { // in alphabetical order\n    rt_args.test();\n    rt_atomics.test();\n    rt_backtrace.test();\n    rt_clipboard.test();\n    rt_clock.test();\n    rt_config.test();\n    rt_debug.test();\n    rt_event.test();\n    rt_files.test();\n    rt_generics.test();\n    rt_heap.test();\n    rt_loader.test();\n    rt_mem.test();\n    rt_mutex.test();\n    rt_num.test();\n    rt_processes.test();\n    rt_static_init_test();\n    rt_str.test();\n    rt_streams.test();\n    rt_thread.test();\n    rt_vigil.test();\n    rt_worker.test();\n}\n\n#else\n\nstatic void rt_core_test(void) { }\n\n#endif\n\nrt_core_if rt_core = {\n    .err     = rt_core_err,\n    .set_err = rt_core_seterr,\n    .abort   = rt_core_abort,\n    .exit    = rt_core_exit,\n    .test    = rt_core_test,\n    .error   = {                                              // posix\n        .access_denied          = ERROR_ACCESS_DENIED,        // EACCES\n        .bad_file               = ERROR_BAD_FILE_TYPE,        // EBADF\n        .broken_pipe            = ERROR_BROKEN_PIPE,          // EPIPE\n        .device_not_ready       = ERROR_NOT_READY,            // ENXIO\n        .directory_not_empty    = ERROR_DIR_NOT_EMPTY,        // ENOTEMPTY\n        .disk_full              = ERROR_DISK_FULL,            // ENOSPC\n        .file_exists            = ERROR_FILE_EXISTS,          // EEXIST\n        .file_not_found         = ERROR_FILE_NOT_FOUND,       // ENOENT\n        .insufficient_buffer    = ERROR_INSUFFICIENT_BUFFER,  // E2BIG\n        .interrupted            = ERROR_OPERATION_ABORTED,    // EINTR\n        .invalid_data           = ERROR_INVALID_DATA,         // EINVAL\n        .invalid_handle         = ERROR_INVALID_HANDLE,       // EBADF\n        .invalid_parameter      = ERROR_INVALID_PARAMETER,    // EINVAL\n        .io_error               = ERROR_IO_DEVICE,            // EIO\n        .more_data              = ERROR_MORE_DATA,            // ENOBUFS\n        .name_too_long          = ERROR_FILENAME_EXCED_RANGE, // ENAMETOOLONG\n        .no_child_process       = ERROR_NO_PROC_SLOTS,        // ECHILD\n        .not_a_directory        = ERROR_DIRECTORY,            // ENOTDIR\n        .not_empty              = ERROR_DIR_NOT_EMPTY,        // ENOTEMPTY\n        .out_of_memory          = ERROR_OUTOFMEMORY,          // ENOMEM\n        .path_not_found         = ERROR_PATH_NOT_FOUND,       // ENOENT\n        .pipe_not_connected     = ERROR_PIPE_NOT_CONNECTED,   // EPIPE\n        .read_only_file         = ERROR_WRITE_PROTECT,        // EROFS\n        .resource_deadlock      = ERROR_LOCK_VIOLATION,       // EDEADLK\n        .too_many_open_files    = ERROR_TOO_MANY_OPEN_FILES,  // EMFILE\n    }\n};\n\n#pragma comment(lib, \"advapi32\")\n#pragma comment(lib, \"ntdll\")\n#pragma comment(lib, \"psapi\")\n#pragma comment(lib, \"shell32\")\n#pragma comment(lib, \"shlwapi\")\n#pragma comment(lib, \"kernel32\")\n#pragma comment(lib, \"user32\") // clipboard\n#pragma comment(lib, \"imm32\")  // Internationalization input method\n#pragma comment(lib, \"ole32\")  // rt_files.known_folder CoMemFree\n#pragma comment(lib, \"dbghelp\")\n#pragma comment(lib, \"imagehlp\")\n\n\n"
  },
  {
    "path": "src/rt/rt_debug.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic const char* rt_debug_abbreviate(const char* file) {\n    const char* fn = strrchr(file, '\\\\');\n    if (fn == null) { fn = strrchr(file, '/'); }\n    return fn != null ? fn + 1 : file;\n}\n\n#ifdef WINDOWS\n\nstatic int32_t rt_debug_max_file_line;\nstatic int32_t rt_debug_max_function;\n\nstatic void rt_debug_output(const char* s, int32_t count) {\n    bool intercepted = false;\n    if (rt_debug.tee != null) { intercepted = rt_debug.tee(s, count); }\n    if (!intercepted) {\n        // For link.exe /Subsystem:Windows code stdout/stderr are often closed\n        if (stderr != null && fileno(stderr) >= 0) {\n            fprintf(stderr, \"%s\", s);\n        }\n        // SetConsoleCP(CP_UTF8) is not guaranteed to be called\n        uint16_t* wide = rt_stackalloc((count + 1) * sizeof(uint16_t));\n        rt_str.utf8to16(wide, count, s, -1);\n        OutputDebugStringW(wide);\n    }\n}\n\nstatic void rt_debug_println_va(const char* file, int32_t line, const char* func,\n        const char* format, va_list va) {\n    if (func == null) { func = \"\"; }\n    char file_line[1024];\n    if (line == 0 && file == null || file[0] == 0x00) {\n        file_line[0] = 0x00;\n    } else {\n        if (file == null) { file = \"\"; } // backtrace can have null files\n        // full path is useful in MSVC debugger output pane (clickable)\n        // for all other scenarios short filename without path is preferable:\n        const char* name = IsDebuggerPresent() ? file : rt_files.basename(file);\n        snprintf(file_line, rt_countof(file_line) - 1, \"%s(%d):\", name, line);\n    }\n    file_line[rt_countof(file_line) - 1] = 0x00; // always zero terminated'\n    rt_debug_max_file_line = rt_max(rt_debug_max_file_line,\n                                    (int32_t)strlen(file_line));\n    rt_debug_max_function  = rt_max(rt_debug_max_function,\n                                    (int32_t)strlen(func));\n    char prefix[2 * 1024];\n    // snprintf() does not guarantee zero termination on truncation\n    snprintf(prefix, rt_countof(prefix) - 1, \"%-*s %-*s\",\n            rt_debug_max_file_line, file_line,\n            rt_debug_max_function,  func);\n    prefix[rt_countof(prefix) - 1] = 0; // zero terminated\n    char text[2 * 1024];\n    if (format != null && format[0] != 0) {\n        #if defined(__GNUC__) || defined(__clang__)\n        #pragma GCC diagnostic push\n        #pragma GCC diagnostic ignored \"-Wformat-nonliteral\"\n        #endif\n        vsnprintf(text, rt_countof(text) - 1, format, va);\n        text[rt_countof(text) - 1] = 0;\n        #if defined(__GNUC__) || defined(__clang__)\n        #pragma GCC diagnostic pop\n        #endif\n    } else {\n        text[0] = 0;\n    }\n    char output[4 * 1024];\n    snprintf(output, rt_countof(output) - 1, \"%s %s\", prefix, text);\n    output[rt_countof(output) - 2] = 0;\n    // strip trailing \\n which can be remnant of fprintf(\"...\\n\")\n    int32_t n = (int32_t)strlen(output);\n    while (n > 0 && (output[n - 1] == '\\n' || output[n - 1] == '\\r')) {\n        output[n - 1] = 0;\n        n--;\n    }\n    rt_assert(n + 1 < rt_countof(output));\n    // Win32 OutputDebugString() needs \\n\n    output[n + 0] = '\\n';\n    output[n + 1] = 0;\n    rt_debug.output(output, n + 2); // including 0x00\n}\n\n#else // posix version:\n\nstatic void rt_debug_vprintf(const char* file, int32_t line, const char* func,\n        const char* format, va_list va) {\n    fprintf(stderr, \"%s(%d): %s \", file, line, func);\n    vfprintf(stderr, format, va);\n    fprintf(stderr, \"\\n\");\n}\n\n#endif\n\nstatic void rt_debug_perrno(const char* file, int32_t line,\n    const char* func, int32_t err_no, const char* format, ...) {\n    if (err_no != 0) {\n        if (format != null && format[0] != 0) {\n            va_list va;\n            va_start(va, format);\n            rt_debug.println_va(file, line, func, format, va);\n            va_end(va);\n        }\n        rt_debug.println(file, line, func, \"errno: %d %s\", err_no, strerror(err_no));\n    }\n}\n\nstatic void rt_debug_perror(const char* file, int32_t line,\n    const char* func, int32_t error, const char* format, ...) {\n    if (error != 0) {\n        if (format != null && format[0] != 0) {\n            va_list va;\n            va_start(va, format);\n            rt_debug.println_va(file, line, func, format, va);\n            va_end(va);\n        }\n        rt_debug.println(file, line, func, \"error: %s\", rt_strerr(error));\n    }\n}\n\nstatic void rt_debug_println(const char* file, int32_t line, const char* func,\n        const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_debug.println_va(file, line, func, format, va);\n    va_end(va);\n}\n\nstatic bool rt_debug_is_debugger_present(void) { return IsDebuggerPresent(); }\n\nstatic void rt_debug_breakpoint(void) {\n    if (rt_debug.is_debugger_present()) { DebugBreak(); }\n}\n\nstatic errno_t rt_debug_raise(uint32_t exception) {\n    rt_core.set_err(0);\n    RaiseException(exception, EXCEPTION_NONCONTINUABLE, 0, null);\n    return rt_core.err();\n}\n\nstatic int32_t rt_debug_verbosity_from_string(const char* s) {\n    char* n = null;\n    long v = strtol(s, &n, 10);\n    if (stricmp(s, \"quiet\") == 0) {\n        return rt_debug.verbosity.quiet;\n    } else if (stricmp(s, \"info\") == 0) {\n        return rt_debug.verbosity.info;\n    } else if (stricmp(s, \"verbose\") == 0) {\n        return rt_debug.verbosity.verbose;\n    } else if (stricmp(s, \"debug\") == 0) {\n        return rt_debug.verbosity.debug;\n    } else if (stricmp(s, \"trace\") == 0) {\n        return rt_debug.verbosity.trace;\n    } else if (n > s && rt_debug.verbosity.quiet <= v &&\n               v <= rt_debug.verbosity.trace) {\n        return v;\n    } else {\n        rt_fatal(\"invalid verbosity: %s\", s);\n        return rt_debug.verbosity.quiet;\n    }\n}\n\nstatic void rt_debug_test(void) {\n    #ifdef RT_TESTS\n    // not clear what can be tested here\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\n#ifndef STATUS_POSSIBLE_DEADLOCK\n#define STATUS_POSSIBLE_DEADLOCK 0xC0000194uL\n#endif\n\nrt_debug_if rt_debug = {\n    .verbosity = {\n        .level   =  0,\n        .quiet   =  0,\n        .info    =  1,\n        .verbose =  2,\n        .debug   =  3,\n        .trace   =  4,\n    },\n    .verbosity_from_string = rt_debug_verbosity_from_string,\n    .tee                   = null,\n    .output                = rt_debug_output,\n    .println               = rt_debug_println,\n    .println_va            = rt_debug_println_va,\n    .perrno                = rt_debug_perrno,\n    .perror                = rt_debug_perror,\n    .is_debugger_present   = rt_debug_is_debugger_present,\n    .breakpoint            = rt_debug_breakpoint,\n    .raise                 = rt_debug_raise,\n    .exception             = {\n        .access_violation        = EXCEPTION_ACCESS_VIOLATION,\n        .datatype_misalignment   = EXCEPTION_DATATYPE_MISALIGNMENT,\n        .breakpoint              = EXCEPTION_BREAKPOINT,\n        .single_step             = EXCEPTION_SINGLE_STEP,\n        .array_bounds            = EXCEPTION_ARRAY_BOUNDS_EXCEEDED,\n        .float_denormal_operand  = EXCEPTION_FLT_DENORMAL_OPERAND,\n        .float_divide_by_zero    = EXCEPTION_FLT_DIVIDE_BY_ZERO,\n        .float_inexact_result    = EXCEPTION_FLT_INEXACT_RESULT,\n        .float_invalid_operation = EXCEPTION_FLT_INVALID_OPERATION,\n        .float_overflow          = EXCEPTION_FLT_OVERFLOW,\n        .float_stack_check       = EXCEPTION_FLT_STACK_CHECK,\n        .float_underflow         = EXCEPTION_FLT_UNDERFLOW,\n        .int_divide_by_zero      = EXCEPTION_INT_DIVIDE_BY_ZERO,\n        .int_overflow            = EXCEPTION_INT_OVERFLOW,\n        .priv_instruction        = EXCEPTION_PRIV_INSTRUCTION,\n        .in_page_error           = EXCEPTION_IN_PAGE_ERROR,\n        .illegal_instruction     = EXCEPTION_ILLEGAL_INSTRUCTION,\n        .noncontinuable          = EXCEPTION_NONCONTINUABLE_EXCEPTION,\n        .stack_overflow          = EXCEPTION_STACK_OVERFLOW,\n        .invalid_disposition     = EXCEPTION_INVALID_DISPOSITION,\n        .guard_page              = EXCEPTION_GUARD_PAGE,\n        .invalid_handle          = EXCEPTION_INVALID_HANDLE,\n        .possible_deadlock       = EXCEPTION_POSSIBLE_DEADLOCK\n    },\n    .test                  = rt_debug_test\n};\n"
  },
  {
    "path": "src/rt/rt_files.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// TODO: test FILE_APPEND_DATA\n// https://learn.microsoft.com/en-us/windows/win32/fileio/appending-one-file-to-another-file?redirectedfrom=MSDN\n\n// are posix and Win32 seek in agreement?\nrt_static_assertion(SEEK_SET == FILE_BEGIN);\nrt_static_assertion(SEEK_CUR == FILE_CURRENT);\nrt_static_assertion(SEEK_END == FILE_END);\n\n#ifndef O_SYNC\n#define O_SYNC (0x10000)\n#endif\n\nstatic errno_t rt_files_open(rt_file_t* *file, const char* fn, int32_t f) {\n    DWORD access = (f & rt_files.o_wr) ? GENERIC_WRITE :\n                   (f & rt_files.o_rw) ? GENERIC_READ | GENERIC_WRITE :\n                                      GENERIC_READ;\n    access |= (f & rt_files.o_append) ? FILE_APPEND_DATA : 0;\n    DWORD disposition =\n        (f & rt_files.o_create) ? ((f & rt_files.o_excl)  ? CREATE_NEW :\n                                (f & rt_files.o_trunc) ? CREATE_ALWAYS :\n                                                      OPEN_ALWAYS) :\n            (f & rt_files.o_trunc) ? TRUNCATE_EXISTING : OPEN_EXISTING;\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    DWORD attr = FILE_ATTRIBUTE_NORMAL;\n    attr |= (f & O_SYNC) ? FILE_FLAG_WRITE_THROUGH : 0;\n    *file = CreateFileA(fn, access, share, null, disposition, attr, null);\n    return *file != INVALID_HANDLE_VALUE ? 0 : rt_core.err();\n}\n\nstatic bool rt_files_is_valid(rt_file_t* file) { // both null and rt_files.invalid\n    return file != rt_files.invalid && file != null;\n}\n\nstatic errno_t rt_files_seek(rt_file_t* file, int64_t *position, int32_t method) {\n    LARGE_INTEGER distance_to_move = { .QuadPart = *position };\n    LARGE_INTEGER p = { 0 }; // pointer\n    errno_t r = rt_b2e(SetFilePointerEx(file, distance_to_move, &p, (DWORD)method));\n    if (r == 0) { *position = p.QuadPart; }\n    return r;\n}\n\nstatic inline uint64_t rt_files_ft_to_us(FILETIME ft) { // us (microseconds)\n    return (ft.dwLowDateTime | (((uint64_t)ft.dwHighDateTime) << 32)) / 10;\n}\n\nstatic int64_t rt_files_a2t(DWORD a) {\n    int64_t type = 0;\n    if (a & FILE_ATTRIBUTE_REPARSE_POINT) {\n        type |= rt_files.type_symlink;\n    }\n    if (a & FILE_ATTRIBUTE_DIRECTORY) {\n        type |= rt_files.type_folder;\n    }\n    if (a & FILE_ATTRIBUTE_DEVICE) {\n        type |= rt_files.type_device;\n    }\n    return type;\n}\n\n#ifdef FILES_LINUX_PATH_BY_FD\n\nstatic int get_final_path_name_by_fd(int fd, char *buffer, int32_t bytes) {\n    swear(bytes >= 0);\n    char fd_path[16 * 1024];\n    // /proc/self/fd/* is a symbolic link\n    snprintf(fd_path, sizeof(fd_path), \"/proc/self/fd/%d\", fd);\n    size_t len = readlink(fd_path, buffer, bytes - 1);\n    if (len != -1) { buffer[len] = 0x00; } // Null-terminate the result\n    return len == -1 ? errno : 0;\n}\n\n#endif\n\nstatic errno_t rt_files_stat(rt_file_t* file, rt_files_stat_t* s,\n                             bool follow_symlink) {\n    errno_t r = 0;\n    BY_HANDLE_FILE_INFORMATION fi;\n    rt_fatal_win32err(GetFileInformationByHandle(file, &fi));\n    const bool symlink =\n        (fi.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;\n    if (follow_symlink && symlink) {\n        const DWORD flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;\n        DWORD n = GetFinalPathNameByHandleA(file, null, 0, flags);\n        if (n == 0) {\n            r = rt_core.err();\n        } else {\n            char* name = null;\n            r = rt_heap.allocate(null, (void**)&name, (int64_t)n + 2, false);\n            if (r == 0) {\n                n = GetFinalPathNameByHandleA(file, name, n + 1, flags);\n                if (n == 0) {\n                    r = rt_core.err();\n                } else {\n                    rt_file_t* f = rt_files.invalid;\n                    r = rt_files.open(&f, name, rt_files.o_rd);\n                    if (r == 0) { // keep following:\n                        r = rt_files.stat(f, s, follow_symlink);\n                        rt_files.close(f);\n                    }\n                }\n                rt_heap.deallocate(null, name);\n            }\n        }\n    } else {\n        s->size = (int64_t)((uint64_t)fi.nFileSizeLow |\n                          (((uint64_t)fi.nFileSizeHigh) << 32));\n        s->created  = rt_files_ft_to_us(fi.ftCreationTime); // since epoch\n        s->accessed = rt_files_ft_to_us(fi.ftLastAccessTime);\n        s->updated  = rt_files_ft_to_us(fi.ftLastWriteTime);\n        s->type = rt_files_a2t(fi.dwFileAttributes);\n    }\n    return r;\n}\n\nstatic errno_t rt_files_read(rt_file_t* file, void* data, int64_t bytes, int64_t *transferred) {\n    errno_t r = 0;\n    *transferred = 0;\n    while (bytes > 0 && r == 0) {\n        DWORD chunk_size = (DWORD)(bytes > UINT32_MAX ? UINT32_MAX : bytes);\n        DWORD bytes_read = 0;\n        r = rt_b2e(ReadFile(file, data, chunk_size, &bytes_read, null));\n        if (r == 0) {\n            *transferred += bytes_read;\n            bytes -= bytes_read;\n            data = (uint8_t*)data + bytes_read;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_files_write(rt_file_t* file, const void* data, int64_t bytes, int64_t *transferred) {\n    errno_t r = 0;\n    *transferred = 0;\n    while (bytes > 0 && r == 0) {\n        DWORD chunk_size = (DWORD)(bytes > UINT32_MAX ? UINT32_MAX : bytes);\n        DWORD bytes_read = 0;\n        r = rt_b2e(WriteFile(file, data, chunk_size, &bytes_read, null));\n        if (r == 0) {\n            *transferred += bytes_read;\n            bytes -= bytes_read;\n            data = (const uint8_t*)data + bytes_read;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_files_flush(rt_file_t* file) {\n    return rt_b2e(FlushFileBuffers(file));\n}\n\nstatic void rt_files_close(rt_file_t* file) {\n    rt_win32_close_handle(file);\n}\n\nstatic errno_t rt_files_write_fully(const char* filename, const void* data,\n                                 int64_t bytes, int64_t *transferred) {\n    if (transferred != null) { *transferred = 0; }\n    errno_t r = 0;\n    const DWORD access = GENERIC_WRITE;\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    const DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH;\n    HANDLE file = CreateFileA(filename, access, share, null, CREATE_ALWAYS,\n                              flags, null);\n    if (file == INVALID_HANDLE_VALUE) {\n        r = rt_core.err();\n    } else {\n        int64_t written = 0;\n        const uint8_t* p = (const uint8_t*)data;\n        while (r == 0 && bytes > 0) {\n            uint64_t write = bytes >= UINT32_MAX ?\n                (uint64_t)(UINT32_MAX) - 0xFFFFuLL : (uint64_t)bytes;\n            rt_assert(0 < write && write < (uint64_t)UINT32_MAX);\n            DWORD chunk = 0;\n            r = rt_b2e(WriteFile(file, p, (DWORD)write, &chunk, null));\n            written += chunk;\n            bytes -= chunk;\n        }\n        if (transferred != null) { *transferred = written; }\n        errno_t rc = rt_b2e(FlushFileBuffers(file));\n        if (r == 0) { r = rc; }\n        rt_win32_close_handle(file);\n    }\n    return r;\n}\n\nstatic errno_t rt_files_unlink(const char* pathname) {\n    if (rt_files.is_folder(pathname)) {\n        return rt_b2e(RemoveDirectoryA(pathname));\n    } else {\n        return rt_b2e(DeleteFileA(pathname));\n    }\n}\n\nstatic errno_t rt_files_create_tmp(char* fn, int32_t count) {\n    // create temporary file (not folder!) see folders_test() about racing\n    rt_swear(fn != null && count > 0);\n    const char* tmp = rt_files.tmp();\n    errno_t r = 0;\n    if (count < (int32_t)strlen(tmp) + 8) {\n        r = ERROR_BUFFER_OVERFLOW;\n    } else {\n        rt_assert(count > (int32_t)strlen(tmp) + 8);\n        // If GetTempFileNameA() succeeds, the return value is the length,\n        // in chars, of the string copied to lpBuffer, not including the\n        // terminating null character.If the function fails,\n        // the return value is zero.\n        if (count > (int32_t)strlen(tmp) + 8) {\n            char prefix[4] = { 0 };\n            r = GetTempFileNameA(tmp, prefix, 0, fn) == 0 ? rt_core.err() : 0;\n            if (r == 0) {\n                rt_assert(rt_files.exists(fn) && !rt_files.is_folder(fn));\n            } else {\n                rt_println(\"GetTempFileNameA() failed %s\", rt_strerr(r));\n            }\n        } else {\n            r = ERROR_BUFFER_OVERFLOW;\n        }\n    }\n    return r;\n}\n\n#pragma push_macro(\"files_acl_args\")\n#pragma push_macro(\"files_get_acl\")\n#pragma push_macro(\"files_set_acl\")\n\n#define rt_files_acl_args(acl) DACL_SECURITY_INFORMATION, null, null, acl, null\n\n#define rt_files_get_acl(obj, type, acl, sd) (errno_t)(         \\\n    (type == SE_FILE_OBJECT ? GetNamedSecurityInfoA((char*)obj, \\\n             SE_FILE_OBJECT, rt_files_acl_args(acl), &sd) :     \\\n    (type == SE_KERNEL_OBJECT) ? GetSecurityInfo((HANDLE)obj,   \\\n             SE_KERNEL_OBJECT, rt_files_acl_args(acl), &sd) :   \\\n    ERROR_INVALID_PARAMETER))\n\n#define rt_files_set_acl(obj, type, acl) (errno_t)(             \\\n    (type == SE_FILE_OBJECT ? SetNamedSecurityInfoA((char*)obj, \\\n             SE_FILE_OBJECT, rt_files_acl_args(acl)) :          \\\n    (type == SE_KERNEL_OBJECT) ? SetSecurityInfo((HANDLE)obj,   \\\n             SE_KERNEL_OBJECT, rt_files_acl_args(acl)) :        \\\n    ERROR_INVALID_PARAMETER))\n\nstatic errno_t rt_files_acl_add_ace(ACL* acl, SID* sid, uint32_t mask,\n                                 ACL** free_me, byte flags) {\n    ACL_SIZE_INFORMATION info = {0};\n    ACL* bigger = null;\n    uint32_t bytes_needed = sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(sid)\n                          - sizeof(DWORD);\n    errno_t r = rt_b2e(GetAclInformation(acl, &info, sizeof(ACL_SIZE_INFORMATION),\n        AclSizeInformation));\n    if (r == 0 && info.AclBytesFree < bytes_needed) {\n        const int64_t bytes = (int64_t)(info.AclBytesInUse + bytes_needed);\n        r = rt_heap.allocate(null, (void**)&bigger, bytes, true);\n        if (r == 0) {\n            r = rt_b2e(InitializeAcl((ACL*)bigger,\n                    info.AclBytesInUse + bytes_needed, ACL_REVISION));\n        }\n    }\n    if (r == 0 && bigger != null) {\n        for (int32_t i = 0; i < (int32_t)info.AceCount; i++) {\n            ACCESS_ALLOWED_ACE* ace = null;\n            r = rt_b2e(GetAce(acl, (DWORD)i, (void**)&ace));\n            if (r != 0) { break; }\n            r = rt_b2e(AddAce(bigger, ACL_REVISION, MAXDWORD, ace,\n                           ace->Header.AceSize));\n            if (r != 0) { break; }\n        }\n    }\n    if (r == 0) {\n        ACCESS_ALLOWED_ACE* ace = null;\n        r = rt_heap.allocate(null, (void**)&ace, bytes_needed, true);\n        if (r == 0) {\n            ace->Header.AceFlags = flags;\n            ace->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;\n            ace->Header.AceSize = (WORD)bytes_needed;\n            ace->Mask = mask;\n            ace->SidStart = sizeof(ACCESS_ALLOWED_ACE);\n            memcpy(&ace->SidStart, sid, GetLengthSid(sid));\n            r = rt_b2e(AddAce(bigger != null ? bigger : acl, ACL_REVISION, MAXDWORD,\n                           ace, bytes_needed));\n            rt_heap.deallocate(null, ace);\n        }\n    }\n    *free_me = bigger;\n    return r;\n}\n\nstatic errno_t rt_files_lookup_sid(ACCESS_ALLOWED_ACE* ace) {\n    // handy for debugging\n    SID* sid = (SID*)&ace->SidStart;\n    DWORD l1 = 128, l2 = 128;\n    char account[128];\n    char group[128];\n    SID_NAME_USE use;\n    errno_t r = rt_b2e(LookupAccountSidA(null, sid, account,\n                                     &l1, group, &l2, &use));\n    if (r == 0) {\n        rt_println(\"%s/%s: type: %d, mask: 0x%X, flags:%d\",\n                group, account,\n                ace->Header.AceType, ace->Mask, ace->Header.AceFlags);\n    } else {\n        rt_println(\"LookupAccountSidA() failed %s\", rt_strerr(r));\n    }\n    return r;\n}\n\nstatic errno_t rt_files_add_acl_ace(void* obj, int32_t obj_type,\n                                 int32_t sid_type, uint32_t mask) {\n    uint8_t stack[SECURITY_MAX_SID_SIZE] = {0};\n    DWORD n = rt_countof(stack);\n    SID* sid = (SID*)stack;\n    errno_t r = rt_b2e(CreateWellKnownSid((WELL_KNOWN_SID_TYPE)sid_type,\n                                       null, sid, &n));\n    if (r != 0) {\n        return ERROR_INVALID_PARAMETER;\n    }\n    ACL* acl = null;\n    void* sd = null;\n    r = rt_files_get_acl(obj, obj_type, &acl, sd);\n    if (r == 0) {\n        ACCESS_ALLOWED_ACE* found = null;\n        for (int32_t i = 0; i < acl->AceCount; i++) {\n            ACCESS_ALLOWED_ACE* ace = null;\n            r = rt_b2e(GetAce(acl, (DWORD)i, (void**)&ace));\n            if (r != 0) { break; }\n            if (EqualSid((SID*)&ace->SidStart, sid)) {\n                if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE &&\n                   (ace->Header.AceFlags & INHERITED_ACE) == 0) {\n                    found = ace;\n                } else if (ace->Header.AceType !=\n                           ACCESS_ALLOWED_ACE_TYPE) {\n                    rt_println(\"%d ACE_TYPE is not supported.\",\n                             ace->Header.AceType);\n                    r = ERROR_INVALID_PARAMETER;\n                }\n                break;\n            }\n        }\n        if (r == 0 && found) {\n            if ((found->Mask & mask) != mask) {\n//              rt_println(\"updating existing ace\");\n                found->Mask |= mask;\n                r = rt_files_set_acl(obj, obj_type, acl);\n            } else {\n//              rt_println(\"desired access is already allowed by ace\");\n            }\n        } else if (r == 0) {\n//          rt_println(\"inserting new ace\");\n            ACL* new_acl = null;\n            byte flags = obj_type == SE_FILE_OBJECT ?\n                CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;\n            r = rt_files_acl_add_ace(acl, sid, mask, &new_acl, flags);\n            if (r == 0) {\n                r = rt_files_set_acl(obj, obj_type, (new_acl != null ? new_acl : acl));\n            }\n            if (new_acl != null) { rt_heap.deallocate(null, new_acl); }\n        }\n    }\n    if (sd != null) { LocalFree(sd); }\n    return r;\n}\n\n#pragma pop_macro(\"files_set_acl\")\n#pragma pop_macro(\"files_get_acl\")\n#pragma pop_macro(\"files_acl_args\")\n\nstatic errno_t rt_files_chmod777(const char* pathname) {\n    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;\n    PSID everyone = null; // Create a well-known SID for the Everyone group.\n    rt_fatal_win32err(AllocateAndInitializeSid(&SIDAuthWorld, 1,\n             SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyone));\n    EXPLICIT_ACCESSA ea[1] = { { 0 } };\n    // Initialize an EXPLICIT_ACCESS structure for an ACE.\n    ea[0].grfAccessPermissions = 0xFFFFFFFF;\n    ea[0].grfAccessMode  = GRANT_ACCESS; // The ACE will allow everyone all access.\n    ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;\n    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;\n    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;\n    ea[0].Trustee.ptstrName  = (LPSTR)everyone;\n    // Create a new ACL that contains the new ACEs.\n    ACL* acl = null;\n    rt_fatal_if_error(SetEntriesInAclA(1, ea, null, &acl));\n    // Initialize a security descriptor.\n    uint8_t stack[SECURITY_DESCRIPTOR_MIN_LENGTH] = {0};\n    SECURITY_DESCRIPTOR* sd = (SECURITY_DESCRIPTOR*)stack;\n    rt_fatal_win32err(InitializeSecurityDescriptor(sd,\n        SECURITY_DESCRIPTOR_REVISION));\n    // Add the ACL to the security descriptor.\n    rt_fatal_win32err(SetSecurityDescriptorDacl(sd,\n        /* present flag: */ true, acl, /* not a default DACL: */  false));\n    // Change the security attributes\n    errno_t r = rt_b2e(SetFileSecurityA(pathname, DACL_SECURITY_INFORMATION, sd));\n    if (r != 0) {\n        rt_println(\"chmod777(%s) failed %s\", pathname, rt_strerr(r));\n    }\n    if (everyone != null) { FreeSid(everyone); }\n    if (acl != null) { LocalFree(acl); }\n    return r;\n}\n\n// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectorya\n// \"If lpSecurityAttributes is null, the directory gets a default security\n//  descriptor. The ACLs in the default security descriptor for a directory\n//  are inherited from its parent directory.\"\n\nstatic errno_t rt_files_mkdirs(const char* dir) {\n    const int32_t n = (int32_t)strlen(dir) + 1;\n    char* s = null;\n    errno_t r = rt_heap.allocate(null, (void**)&s, n, true);\n    const char* next = strchr(dir, '\\\\');\n    if (next == null) { next = strchr(dir, '/'); }\n    while (r == 0 && next != null) {\n        if (next > dir && *(next - 1) != ':') {\n            memcpy(s, dir, (size_t)(next - dir));\n            r = rt_b2e(CreateDirectoryA(s, null));\n            if (r == ERROR_ALREADY_EXISTS) { r = 0; }\n        }\n        if (r == 0) {\n            const char* prev = ++next;\n            next = strchr(prev, '\\\\');\n            if (next == null) { next = strchr(prev, '/'); }\n        }\n    }\n    if (r == 0) {\n        r = rt_b2e(CreateDirectoryA(dir, null));\n    }\n    rt_heap.deallocate(null, s);\n    return r == ERROR_ALREADY_EXISTS ? 0 : r;\n}\n\n#pragma push_macro(\"rt_files_realloc_path\")\n#pragma push_macro(\"rt_files_append_name\")\n\n#define rt_files_realloc_path(r, pn, pnc, fn, name) do {                \\\n    const int32_t bytes = (int32_t)(strlen(fn) + strlen(name) + 3);     \\\n    if (bytes > pnc) {                                                  \\\n        r = rt_heap.reallocate(null, (void**)&pn, bytes, false);        \\\n        if (r != 0) {                                                   \\\n            pnc = bytes;                                                \\\n        } else {                                                        \\\n            rt_heap.deallocate(null, pn);                               \\\n            pn = null;                                                  \\\n        }                                                               \\\n    }                                                                   \\\n} while (0)\n\n#define rt_files_append_name(pn, pnc, fn, name) do {     \\\n    if (strcmp(fn, \"\\\\\") == 0 || strcmp(fn, \"/\") == 0) { \\\n        rt_str.format(pn, pnc, \"\\\\%s\", name);            \\\n    } else {                                             \\\n        rt_str.format(pn, pnc, \"%.*s\\\\%s\", k, fn, name); \\\n    }                                                    \\\n} while (0)\n\nstatic errno_t rt_files_rmdirs(const char* fn) {\n    rt_files_stat_t st;\n    rt_folder_t folder;\n    errno_t r = rt_files.opendir(&folder, fn);\n    if (r == 0) {\n        int32_t k = (int32_t)strlen(fn);\n        // remove trailing backslash (except if it is root: \"/\" or \"\\\\\")\n        if (k > 1 && (fn[k - 1] == '/' || fn[k - 1] == '\\\\')) {\n            k--;\n        }\n        int32_t pnc = 64 * 1024; // pathname \"pn\" capacity in bytes\n        char* pn = null;\n        r = rt_heap.allocate(null, (void**)&pn, pnc, false);\n        while (r == 0) {\n            // recurse into sub folders and remove them first\n            // do NOT follow symlinks - it could be disastrous\n            const char* name = rt_files.readdir(&folder, &st);\n            if (name == null) { break; }\n            if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0 &&\n                (st.type & rt_files.type_symlink) == 0 &&\n                (st.type & rt_files.type_folder) != 0) {\n                rt_files_realloc_path(r, pn, pnc, fn, name);\n                if (r == 0) {\n                    rt_files_append_name(pn, pnc, fn, name);\n                    r = rt_files.rmdirs(pn);\n                }\n            }\n        }\n        rt_files.closedir(&folder);\n        r = rt_files.opendir(&folder, fn);\n        while (r == 0) {\n            const char* name = rt_files.readdir(&folder, &st);\n            if (name == null) { break; }\n            // symlinks are already removed as normal files\n            if (strcmp(name, \".\") != 0 && strcmp(name, \"..\") != 0 &&\n                (st.type & rt_files.type_folder) == 0) {\n                rt_files_realloc_path(r, pn, pnc, fn, name);\n                if (r == 0) {\n                    rt_files_append_name(pn, pnc, fn, name);\n                    r = rt_files.unlink(pn);\n                    if (r != 0) {\n                        rt_println(\"remove(%s) failed %s\", pn, rt_strerr(r));\n                    }\n                }\n            }\n        }\n        rt_heap.deallocate(null, pn);\n        rt_files.closedir(&folder);\n    }\n    if (r == 0) { r = rt_files.unlink(fn); }\n    return r;\n}\n\n#pragma pop_macro(\"rt_files_append_name\")\n#pragma pop_macro(\"rt_files_realloc_path\")\n\nstatic bool rt_files_exists(const char* path) {\n    return PathFileExistsA(path);\n}\n\nstatic bool rt_files_is_folder(const char* path) {\n    return PathIsDirectoryA(path);\n}\n\nstatic bool rt_files_is_symlink(const char* filename) {\n    DWORD attributes = GetFileAttributesA(filename);\n    return attributes != INVALID_FILE_ATTRIBUTES &&\n          (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;\n}\n\nstatic const char* rt_files_basename(const char* pathname) {\n    const char* bn = strrchr(pathname, '\\\\');\n    if (bn == null) { bn = strrchr(pathname, '/'); }\n    return bn != null ? bn + 1 : pathname;\n}\n\nstatic errno_t rt_files_copy(const char* s, const char* d) {\n    return rt_b2e(CopyFileA(s, d, false));\n}\n\nstatic errno_t rt_files_move(const char* s, const char* d) {\n    static const DWORD flags =\n        MOVEFILE_REPLACE_EXISTING |\n        MOVEFILE_COPY_ALLOWED |\n        MOVEFILE_WRITE_THROUGH;\n    return rt_b2e(MoveFileExA(s, d, flags));\n}\n\nstatic errno_t rt_files_link(const char* from, const char* to) {\n    // note reverse order of parameters:\n    return rt_b2e(CreateHardLinkA(to, from, null));\n}\n\nstatic errno_t rt_files_symlink(const char* from, const char* to) {\n    // The correct order of parameters for CreateSymbolicLinkA is:\n    // CreateSymbolicLinkA(symlink_to_create, existing_file, flags);\n    DWORD flags = rt_files.is_folder(from) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;\n    return rt_b2e(CreateSymbolicLinkA(to, from, flags));\n}\n\nstatic const char* rt_files_known_folder(int32_t kf) {\n    // known folder ids order must match enum see:\n    static const GUID* kf_ids[] = {\n        &FOLDERID_Profile,\n        &FOLDERID_Desktop,\n        &FOLDERID_Documents,\n        &FOLDERID_Downloads,\n        &FOLDERID_Music,\n        &FOLDERID_Pictures,\n        &FOLDERID_Videos,\n        &FOLDERID_Public,\n        &FOLDERID_ProgramFiles,\n        &FOLDERID_ProgramData\n    };\n    static rt_file_name_t known_folders[rt_countof(kf_ids)];\n    rt_fatal_if(!(0 <= kf && kf < rt_countof(kf_ids)), \"invalid kf=%d\", kf);\n    if (known_folders[kf].s[0] == 0) {\n        uint16_t* path = null;\n        rt_fatal_if_error(SHGetKnownFolderPath(kf_ids[kf], 0, null, &path));\n        const int32_t n = rt_countof(known_folders[kf].s);\n        rt_str.utf16to8(known_folders[kf].s, n, path, -1);\n        CoTaskMemFree(path);\n\t}\n    return known_folders[kf].s;\n}\n\nstatic const char* rt_files_bin(void) {\n    return rt_files_known_folder(rt_files.folder.bin);\n}\n\nstatic const char* rt_files_data(void) {\n    return rt_files_known_folder(rt_files.folder.data);\n}\n\nstatic const char* rt_files_tmp(void) {\n    static char tmp[rt_files_max_path];\n    if (tmp[0] == 0) {\n        // If GetTempPathA() succeeds, the return value is the length,\n        // in chars, of the string copied to lpBuffer, not including\n        // the terminating null character. If the function fails, the\n        // return value is zero.\n        errno_t r = GetTempPathA(rt_countof(tmp), tmp) == 0 ? rt_core.err() : 0;\n        rt_fatal_if(r != 0, \"GetTempPathA() failed %s\", rt_strerr(r));\n    }\n    return tmp;\n}\n\nstatic errno_t rt_files_cwd(char* fn, int32_t count) {\n    rt_swear(count > 1);\n    DWORD bytes = (DWORD)(count - 1);\n    errno_t r = rt_b2e(GetCurrentDirectoryA(bytes, fn));\n    fn[count - 1] = 0; // always\n    return r;\n}\n\nstatic errno_t rt_files_chdir(const char* fn) {\n    return rt_b2e(SetCurrentDirectoryA(fn));\n}\n\ntypedef struct rt_files_dir_s {\n    HANDLE handle;\n    WIN32_FIND_DATAA find; // On Win64: 320 bytes\n} rt_files_dir_t;\n\nrt_static_assertion(sizeof(rt_files_dir_t) <= sizeof(rt_folder_t));\n\nstatic errno_t rt_files_opendir(rt_folder_t* folder, const char* folder_name) {\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    int32_t n = (int32_t)strlen(folder_name);\n    char* fn = null;\n    // extra room for \"\\*\" suffix\n    errno_t r = rt_heap.allocate(null, (void**)&fn, (int64_t)n + 3, false);\n    if (r == 0) {\n        rt_str.format(fn, n + 3, \"%s\\\\*\", folder_name);\n        fn[n + 2] = 0;\n        d->handle = FindFirstFileA(fn, &d->find);\n        if (d->handle == INVALID_HANDLE_VALUE) { r = rt_core.err(); }\n        rt_heap.deallocate(null, fn);\n    }\n    return r;\n}\n\nstatic uint64_t rt_files_ft2us(FILETIME* ft) { // 100ns units to microseconds:\n    return (((uint64_t)ft->dwHighDateTime) << 32 | ft->dwLowDateTime) / 10;\n}\n\nstatic const char* rt_files_readdir(rt_folder_t* folder, rt_files_stat_t* s) {\n    const char* fn = null;\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    if (FindNextFileA(d->handle, &d->find)) {\n        fn = d->find.cFileName;\n        // Ensure zero termination\n        d->find.cFileName[rt_countof(d->find.cFileName) - 1] = 0x00;\n        if (s != null) {\n            s->accessed = rt_files_ft2us(&d->find.ftLastAccessTime);\n            s->created = rt_files_ft2us(&d->find.ftCreationTime);\n            s->updated = rt_files_ft2us(&d->find.ftLastWriteTime);\n            s->type = rt_files_a2t(d->find.dwFileAttributes);\n            s->size = (int64_t)((((uint64_t)d->find.nFileSizeHigh) << 32) |\n                                  (uint64_t)d->find.nFileSizeLow);\n        }\n    }\n    return fn;\n}\n\nstatic void rt_files_closedir(rt_folder_t* folder) {\n    rt_files_dir_t* d = (rt_files_dir_t*)(void*)folder;\n    rt_fatal_win32err(FindClose(d->handle));\n}\n\n#pragma push_macro(\"files_test_failed\")\n\n#ifdef RT_TESTS\n\n// TODO: change rt_fatal_if() to swear()\n\n#define rt_files_test_failed \" failed %s\", rt_strerr(rt_core.err())\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                       \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                                   \\\n    }                                                           \\\n} while (0)\n\nstatic void folders_dump_time(const char* label, uint64_t us) {\n    int32_t year = 0;\n    int32_t month = 0;\n    int32_t day = 0;\n    int32_t hh = 0;\n    int32_t mm = 0;\n    int32_t ss = 0;\n    int32_t ms = 0;\n    int32_t mc = 0;\n    rt_clock.local(us, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n    rt_println(\"%-7s: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d\",\n            label, year, month, day, hh, mm, ss, ms, mc);\n}\n\nstatic void folders_test(void) {\n    uint64_t now = rt_clock.microseconds(); // microseconds since epoch\n    uint64_t before = now - 1 * (uint64_t)rt_clock.usec_in_sec; // one second earlier\n    uint64_t after  = now + 2 * (uint64_t)rt_clock.usec_in_sec; // two seconds later\n    int32_t year = 0;\n    int32_t month = 0;\n    int32_t day = 0;\n    int32_t hh = 0;\n    int32_t mm = 0;\n    int32_t ss = 0;\n    int32_t ms = 0;\n    int32_t mc = 0;\n    rt_clock.local(now, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n    verbose(\"now: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d\",\n             year, month, day, hh, mm, ss, ms, mc);\n    // Test cwd, setcwd\n    const char* tmp = rt_files.tmp();\n    char cwd[256] = { 0 };\n    rt_fatal_if(rt_files.cwd(cwd, sizeof(cwd)) != 0, \"rt_files.cwd() failed\");\n    rt_fatal_if(rt_files.chdir(tmp) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                tmp, rt_strerr(rt_core.err()));\n    // there is no racing free way to create temporary folder\n    // without having a temporary file for the duration of folder usage:\n    char tmp_file[rt_files_max_path]; // create_tmp() is thread safe race free:\n    errno_t r = rt_files.create_tmp(tmp_file, rt_countof(tmp_file));\n    rt_fatal_if(r != 0, \"rt_files.create_tmp() failed %s\", rt_strerr(r));\n    char tmp_dir[rt_files_max_path];\n    rt_str_printf(tmp_dir, \"%s.dir\", tmp_file);\n    r = rt_files.mkdirs(tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.mkdirs(%s) failed %s\", tmp_dir, rt_strerr(r));\n    verbose(\"%s\", tmp_dir);\n    rt_folder_t folder;\n    char pn[rt_files_max_path] = { 0 };\n    rt_str_printf(pn, \"%s/file\", tmp_dir);\n    // cannot test symlinks because they are only\n    // available to Administrators and in Developer mode\n//  char sym[rt_files_max_path] = { 0 };\n    char hard[rt_files_max_path] = { 0 };\n    char sub[rt_files_max_path] = { 0 };\n    rt_str_printf(hard, \"%s/hard\", tmp_dir);\n    rt_str_printf(sub, \"%s/subd\", tmp_dir);\n    const char* content = \"content\";\n    int64_t transferred = 0;\n    r = rt_files.write_fully(pn, content, (int64_t)strlen(content), &transferred);\n    rt_fatal_if(r != 0, \"rt_files.write_fully(\\\"%s\\\") failed %s\", pn, rt_strerr(r));\n    rt_swear(transferred == (int64_t)strlen(content));\n    r = rt_files.link(pn, hard);\n    rt_fatal_if(r != 0, \"rt_files.link(\\\"%s\\\", \\\"%s\\\") failed %s\",\n                      pn, hard, rt_strerr(r));\n    r = rt_files.mkdirs(sub);\n    rt_fatal_if(r != 0, \"rt_files.mkdirs(\\\"%s\\\") failed %s\", sub, rt_strerr(r));\n    r = rt_files.opendir(&folder, tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.opendir(\\\"%s\\\") failed %s\", tmp_dir, rt_strerr(r));\n    for (;;) {\n        rt_files_stat_t st = { 0 };\n        const char* name = rt_files.readdir(&folder, &st);\n        if (name == null) { break; }\n        uint64_t at = st.accessed;\n        uint64_t ct = st.created;\n        uint64_t ut = st.updated;\n        rt_swear(ct <= at && ct <= ut);\n        rt_clock.local(ct, &year, &month, &day, &hh, &mm, &ss, &ms, &mc);\n        bool is_folder = st.type & rt_files.type_folder;\n        bool is_symlink = st.type & rt_files.type_symlink;\n        int64_t bytes = st.size;\n        verbose(\"%s: %04d-%02d-%02d %02d:%02d:%02d.%03d:%03d %lld bytes %s%s\",\n                name, year, month, day, hh, mm, ss, ms, mc,\n                bytes, is_folder ? \"[folder]\" : \"\", is_symlink ? \"[symlink]\" : \"\");\n        if (strcmp(name, \"file\") == 0 || strcmp(name, \"hard\") == 0) {\n            rt_swear(bytes == (int64_t)strlen(content),\n                    \"size of \\\"%s\\\": %lld is incorrect expected: %d\",\n                    name, bytes, transferred);\n        }\n        if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) {\n            rt_swear(is_folder, \"\\\"%s\\\" is_folder: %d\", name, is_folder);\n        } else {\n            rt_swear((strcmp(name, \"subd\") == 0) == is_folder,\n                  \"\\\"%s\\\" is_folder: %d\", name, is_folder);\n            // empirically timestamps are imprecise on NTFS\n            rt_swear(at >= before, \"access: %lld  >= %lld\", at, before);\n            if (ct < before || ut < before || at >= after || ct >= after || ut >= after) {\n                rt_println(\"file: %s\", name);\n                folders_dump_time(\"before\", before);\n                folders_dump_time(\"create\", ct);\n                folders_dump_time(\"update\", ut);\n                folders_dump_time(\"access\", at);\n            }\n            rt_swear(ct >= before, \"create: %lld  >= %lld\", ct, before);\n            rt_swear(ut >= before, \"update: %lld  >= %lld\", ut, before);\n            // and no later than 2 seconds since folders_test()\n            rt_swear(at < after, \"access: %lld  < %lld\", at, after);\n            rt_swear(ct < after, \"create: %lld  < %lld\", ct, after);\n            rt_swear(at < after, \"update: %lld  < %lld\", ut, after);\n        }\n    }\n    rt_files.closedir(&folder);\n    r = rt_files.rmdirs(tmp_dir);\n    rt_fatal_if(r != 0, \"rt_files.rmdirs(\\\"%s\\\") failed %s\",\n                     tmp_dir, rt_strerr(r));\n    r = rt_files.unlink(tmp_file);\n    rt_fatal_if(r != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                     tmp_file, rt_strerr(r));\n    rt_fatal_if(rt_files.chdir(cwd) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n             cwd, rt_strerr(rt_core.err()));\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"verbose\")\n\nstatic void rt_files_test_append_thread(void* p) {\n    rt_file_t* f = (rt_file_t*)p;\n    uint8_t data[256] = {0};\n    for (int i = 0; i < 256; i++) { data[i] = (uint8_t)i; }\n    int64_t transferred = 0;\n    rt_fatal_if(rt_files.write(f, data, rt_countof(data), &transferred) != 0 ||\n             transferred != rt_countof(data), \"rt_files.write()\" rt_files_test_failed);\n}\n\nstatic void rt_files_test(void) {\n    folders_test();\n    uint64_t now = rt_clock.microseconds(); // epoch time\n    char tf[256]; // temporary file\n    rt_fatal_if(rt_files.create_tmp(tf, rt_countof(tf)) != 0,\n            \"rt_files.create_tmp()\" rt_files_test_failed);\n    uint8_t data[256] = {0};\n    int64_t transferred = 0;\n    for (int i = 0; i < 256; i++) { data[i] = (uint8_t)i; }\n    {\n        rt_file_t* f = rt_files.invalid;\n        rt_fatal_if(rt_files.open(&f, tf,\n                 rt_files.o_wr | rt_files.o_create | rt_files.o_trunc) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_fatal_if(rt_files.write_fully(tf, data, rt_countof(data), &transferred) != 0 ||\n                 transferred != rt_countof(data),\n                \"rt_files.write_fully()\" rt_files_test_failed);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rd) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        for (int32_t i = 0; i < 256; i++) {\n            for (int32_t j = 1; j < 256 - i; j++) {\n                uint8_t test[rt_countof(data)] = { 0 };\n                int64_t position = i;\n                rt_fatal_if(rt_files.seek(f, &position, rt_files.seek_set) != 0 ||\n                         position != i,\n                        \"rt_files.seek(position: %lld) failed %s\",\n                         position, rt_strerr(rt_core.err()));\n                rt_fatal_if(rt_files.read(f, test, j, &transferred) != 0 ||\n                         transferred != j,\n                        \"rt_files.read() transferred: %lld failed %s\",\n                        transferred, rt_strerr(rt_core.err()));\n                for (int32_t k = 0; k < j; k++) {\n                    rt_swear(test[k] == data[i + k],\n                         \"Data mismatch at position: %d, length %d\"\n                         \"test[%d]: 0x%02X != data[%d + %d]: 0x%02X \",\n                          i, j,\n                          k, test[k], i, k, data[i + k]);\n                }\n            }\n        }\n        rt_swear((rt_files.o_rd | rt_files.o_wr) != rt_files.o_rw);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rw) != 0 || !rt_files.is_valid(f),\n                \"rt_files.open()\" rt_files_test_failed);\n        for (int32_t i = 0; i < 256; i++) {\n            uint8_t val = ~data[i];\n            int64_t pos = i;\n            rt_fatal_if(rt_files.seek(f, &pos, rt_files.seek_set) != 0 || pos != i,\n                    \"rt_files.seek() failed %s\", rt_core.err());\n            rt_fatal_if(rt_files.write(f, &val, 1, &transferred) != 0 ||\n                     transferred != 1, \"rt_files.write()\" rt_files_test_failed);\n            pos = i;\n            rt_fatal_if(rt_files.seek(f, &pos, rt_files.seek_set) != 0 || pos != i,\n                    \"rt_files.seek(pos: %lld i: %d) failed %s\", pos, i, rt_core.err());\n            uint8_t read_val = 0;\n            rt_fatal_if(rt_files.read(f, &read_val, 1, &transferred) != 0 ||\n                     transferred != 1, \"rt_files.read()\" rt_files_test_failed);\n            rt_swear(read_val == val, \"Data mismatch at position %d\", i);\n        }\n        rt_files_stat_t s = { 0 };\n        rt_files.stat(f, &s, false);\n        uint64_t before = now - 1 * (uint64_t)rt_clock.usec_in_sec; // one second before now\n        uint64_t after  = now + 2 * (uint64_t)rt_clock.usec_in_sec; // two seconds after\n        rt_swear(before <= s.created  && s.created  <= after,\n             \"before: %lld created: %lld after: %lld\", before, s.created, after);\n        rt_swear(before <= s.accessed && s.accessed <= after,\n             \"before: %lld created: %lld accessed: %lld\", before, s.accessed, after);\n        rt_swear(before <= s.updated  && s.updated  <= after,\n             \"before: %lld created: %lld updated: %lld\", before, s.updated, after);\n        rt_files.close(f);\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_wr | rt_files.o_create | rt_files.o_trunc) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_files.stat(f, &s, false);\n        rt_swear(s.size == 0, \"File is not empty after truncation. .size: %lld\", s.size);\n        rt_files.close(f);\n    }\n    {  // Append test with threads\n        rt_file_t* f = rt_files.invalid;\n        rt_fatal_if(rt_files.open(&f, tf, rt_files.o_rw | rt_files.o_append) != 0 ||\n                !rt_files.is_valid(f), \"rt_files.open()\" rt_files_test_failed);\n        rt_thread_t thread1 = rt_thread.start(rt_files_test_append_thread, f);\n        rt_thread_t thread2 = rt_thread.start(rt_files_test_append_thread, f);\n        rt_thread.join(thread1, -1);\n        rt_thread.join(thread2, -1);\n        rt_files.close(f);\n    }\n    {   // write_fully, exists, is_folder, mkdirs, rmdirs, create_tmp, chmod777\n        rt_fatal_if(rt_files.write_fully(tf, data, rt_countof(data), &transferred) != 0 ||\n                 transferred != rt_countof(data),\n                \"rt_files.write_fully() failed %s\", rt_core.err());\n        rt_fatal_if(!rt_files.exists(tf), \"file \\\"%s\\\" does not exist\", tf);\n        rt_fatal_if(rt_files.is_folder(tf), \"%s is a folder\", tf);\n        rt_fatal_if(rt_files.chmod777(tf) != 0, \"rt_files.chmod777(\\\"%s\\\") failed %s\",\n                 tf, rt_strerr(rt_core.err()));\n        char folder[256] = { 0 };\n        rt_str_printf(folder, \"%s.folder\\\\subfolder\", tf);\n        rt_fatal_if(rt_files.mkdirs(folder) != 0, \"rt_files.mkdirs(\\\"%s\\\") failed %s\",\n            folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.is_folder(folder), \"\\\"%s\\\" is not a folder\", folder);\n        rt_fatal_if(rt_files.chmod777(folder) != 0, \"rt_files.chmod777(\\\"%s\\\") failed %s\",\n                 folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.rmdirs(folder) != 0, \"rt_files.rmdirs(\\\"%s\\\") failed %s\",\n                 folder, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(folder), \"folder \\\"%s\\\" still exists\", folder);\n    }\n    {   // getcwd, chdir\n        const char* tmp = rt_files.tmp();\n        char cwd[256] = { 0 };\n        rt_fatal_if(rt_files.cwd(cwd, sizeof(cwd)) != 0, \"rt_files.cwd() failed\");\n        rt_fatal_if(rt_files.chdir(tmp) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                 tmp, rt_strerr(rt_core.err()));\n        // symlink\n        if (rt_processes.is_elevated()) {\n            char sym_link[rt_files_max_path];\n            rt_str_printf(sym_link, \"%s.sym_link\", tf);\n            rt_fatal_if(rt_files.symlink(tf, sym_link) != 0,\n                \"rt_files.symlink(\\\"%s\\\", \\\"%s\\\") failed %s\",\n                tf, sym_link, rt_strerr(rt_core.err()));\n            rt_fatal_if(!rt_files.is_symlink(sym_link), \"\\\"%s\\\" is not a sym_link\", sym_link);\n            rt_fatal_if(rt_files.unlink(sym_link) != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                    sym_link, rt_strerr(rt_core.err()));\n        } else {\n            rt_println(\"Skipping rt_files.symlink test: process is not elevated\");\n        }\n        // hard link\n        char hard_link[rt_files_max_path];\n        rt_str_printf(hard_link, \"%s.hard_link\", tf);\n        rt_fatal_if(rt_files.link(tf, hard_link) != 0,\n            \"rt_files.link(\\\"%s\\\", \\\"%s\\\") failed %s\",\n            tf, hard_link, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.exists(hard_link), \"\\\"%s\\\" does not exist\", hard_link);\n        rt_fatal_if(rt_files.unlink(hard_link) != 0, \"rt_files.unlink(\\\"%s\\\") failed %s\",\n                 hard_link, rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(hard_link), \"\\\"%s\\\" still exists\", hard_link);\n        // copy, move:\n        rt_fatal_if(rt_files.copy(tf, \"copied_file\") != 0,\n            \"rt_files.copy(\\\"%s\\\", 'copied_file') failed %s\",\n            tf, rt_strerr(rt_core.err()));\n        rt_fatal_if(!rt_files.exists(\"copied_file\"), \"'copied_file' does not exist\");\n        rt_fatal_if(rt_files.move(\"copied_file\", \"moved_file\") != 0,\n            \"rt_files.move('copied_file', 'moved_file') failed %s\",\n            rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.exists(\"copied_file\"), \"'copied_file' still exists\");\n        rt_fatal_if(!rt_files.exists(\"moved_file\"), \"'moved_file' does not exist\");\n        rt_fatal_if(rt_files.unlink(\"moved_file\") != 0,\n                \"rt_files.unlink('moved_file') failed %s\",\n                 rt_strerr(rt_core.err()));\n        rt_fatal_if(rt_files.chdir(cwd) != 0, \"rt_files.chdir(\\\"%s\\\") failed %s\",\n                    cwd, rt_strerr(rt_core.err()));\n    }\n    rt_fatal_if(rt_files.unlink(tf) != 0);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_files_test(void) {}\n\n#endif // RT_TESTS\n\n#pragma pop_macro(\"files_test_failed\")\n\nrt_files_if rt_files = {\n    .invalid  = (rt_file_t*)INVALID_HANDLE_VALUE,\n    // rt_files_stat_t.type:\n    .type_folder  = 0x00000010, // FILE_ATTRIBUTE_DIRECTORY\n    .type_symlink = 0x00000400, // FILE_ATTRIBUTE_REPARSE_POINT\n    .type_device  = 0x00000040, // FILE_ATTRIBUTE_DEVICE\n    // seek() methods:\n    .seek_set = SEEK_SET,\n    .seek_cur = SEEK_CUR,\n    .seek_end = SEEK_END,\n    // open() flags: missing O_RSYNC, O_DSYNC, O_NONBLOCK, O_NOCTTY\n    .o_rd     = O_RDONLY,\n    .o_wr     = O_WRONLY,\n    .o_rw     = O_RDWR,\n    .o_append = O_APPEND,\n    .o_create = O_CREAT,\n    .o_excl   = O_EXCL,\n    .o_trunc  = O_TRUNC,\n    .o_sync   = O_SYNC,\n    // known folders ids:\n    .folder = {\n        .home      = 0, // c:\\Users\\<username>\n        .desktop   = 1,\n        .documents = 2,\n        .downloads = 3,\n        .music     = 4,\n        .pictures  = 5,\n        .videos    = 6,\n        .shared    = 7, // c:\\Users\\Public\n        .bin       = 8, // c:\\Program Files\n        .data      = 9  // c:\\ProgramData\n    },\n    // methods:\n    .open         = rt_files_open,\n    .is_valid     = rt_files_is_valid,\n    .seek         = rt_files_seek,\n    .stat         = rt_files_stat,\n    .read         = rt_files_read,\n    .write        = rt_files_write,\n    .flush        = rt_files_flush,\n    .close        = rt_files_close,\n    .write_fully  = rt_files_write_fully,\n    .exists       = rt_files_exists,\n    .is_folder    = rt_files_is_folder,\n    .is_symlink   = rt_files_is_symlink,\n    .mkdirs       = rt_files_mkdirs,\n    .rmdirs       = rt_files_rmdirs,\n    .create_tmp   = rt_files_create_tmp,\n    .chmod777     = rt_files_chmod777,\n    .unlink       = rt_files_unlink,\n    .link         = rt_files_link,\n    .symlink      = rt_files_symlink,\n    .basename     = rt_files_basename,\n    .copy         = rt_files_copy,\n    .move         = rt_files_move,\n    .cwd          = rt_files_cwd,\n    .chdir        = rt_files_chdir,\n    .known_folder = rt_files_known_folder,\n    .bin          = rt_files_bin,\n    .data         = rt_files_data,\n    .tmp          = rt_files_tmp,\n    .opendir      = rt_files_opendir,\n    .readdir      = rt_files_readdir,\n    .closedir     = rt_files_closedir,\n    .test         = rt_files_test\n};\n"
  },
  {
    "path": "src/rt/rt_generics.c",
    "content": "#include \"rt/rt.h\"\n\n#ifdef RT_TESTS\n\nstatic void rt_generics_test(void) {\n    {\n        int8_t a = 10, b = 20;\n        rt_swear(rt_max(a++, b++) == 20);\n        rt_swear(rt_min(a++, b++) == 11);\n    }\n    {\n        int32_t a = 10, b = 20;\n        rt_swear(rt_max(a++, b++) == 20);\n        rt_swear(rt_min(a++, b++) == 11);\n    }\n    {\n        fp32_t a = 1.1f, b = 2.2f;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp64_t a = 1.1, b = 2.2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp32_t a = 1.1f, b = 2.2f;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        fp64_t a = 1.1, b = 2.2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        char a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        unsigned char a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    // MS cl.exe version 19.39.33523 has issues with \"long\":\n    // does not pick up int32_t/uint32_t types for \"long\" and \"unsigned long\"\n    {\n        long int a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        unsigned long a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    {\n        long long a = 1, b = 2;\n        rt_swear(rt_max(a, b) == b);\n        rt_swear(rt_min(a, b) == a);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_generics_test(void) { }\n\n#endif\n\nrt_generics_if rt_generics = {\n    .test = rt_generics_test\n};\n"
  },
  {
    "path": "src/rt/rt_heap.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n\nstatic errno_t rt_heap_alloc(void* *a, int64_t bytes) {\n    return rt_heap.allocate(null, a, bytes, false);\n}\n\nstatic errno_t rt_heap_alloc_zero(void* *a, int64_t bytes) {\n    return rt_heap.allocate(null, a, bytes, true);\n}\n\nstatic errno_t rt_heap_realloc(void* *a, int64_t bytes) {\n    return rt_heap.reallocate(null, a, bytes, false);\n}\n\nstatic errno_t rt_heap_realloc_zero(void* *a, int64_t bytes) {\n    return rt_heap.reallocate(null, a, bytes, true);\n}\n\nstatic void rt_heap_free(void* a) {\n    rt_heap.deallocate(null, a);\n}\n\nstatic rt_heap_t* rt_heap_create(bool serialized) {\n    const DWORD options = serialized ? 0 : HEAP_NO_SERIALIZE;\n    return (rt_heap_t*)HeapCreate(options, 0, 0);\n}\n\nstatic void rt_heap_dispose(rt_heap_t* h) {\n    rt_fatal_win32err(HeapDestroy((HANDLE)h));\n}\n\nstatic inline HANDLE rt_heap_or_process_heap(rt_heap_t* h) {\n    static HANDLE process_heap;\n    if (process_heap == null) { process_heap = GetProcessHeap(); }\n    return h != null ? (HANDLE)h : process_heap;\n}\n\nstatic errno_t rt_heap_allocate(rt_heap_t* h, void* *p, int64_t bytes, bool zero) {\n    rt_swear(bytes > 0);\n    #ifdef DEBUG\n        static bool enabled;\n        if (!enabled) {\n            enabled = true;\n            HeapSetInformation(null, HeapEnableTerminationOnCorruption, null, 0);\n        }\n    #endif\n    const DWORD flags = zero ? HEAP_ZERO_MEMORY : 0;\n    *p = HeapAlloc(rt_heap_or_process_heap(h), flags, (SIZE_T)bytes);\n    return *p == null ? ERROR_OUTOFMEMORY : 0;\n}\n\nstatic errno_t rt_heap_reallocate(rt_heap_t* h, void* *p, int64_t bytes,\n        bool zero) {\n    rt_swear(bytes > 0);\n    const DWORD flags = zero ? HEAP_ZERO_MEMORY : 0;\n    void* a = *p == null ? // HeapReAlloc(..., null, bytes) may not work\n        HeapAlloc(rt_heap_or_process_heap(h), flags, (SIZE_T)bytes) :\n        HeapReAlloc(rt_heap_or_process_heap(h), flags, *p, (SIZE_T)bytes);\n    if (a != null) { *p = a; }\n    return a == null ? ERROR_OUTOFMEMORY : 0;\n}\n\nstatic void rt_heap_deallocate(rt_heap_t* h, void* a) {\n    rt_fatal_win32err(HeapFree(rt_heap_or_process_heap(h), 0, a));\n}\n\nstatic int64_t rt_heap_bytes(rt_heap_t* h, void* a) {\n    SIZE_T bytes = HeapSize(rt_heap_or_process_heap(h), 0, a);\n    rt_fatal_if(bytes == (SIZE_T)-1);\n    return (int64_t)bytes;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_heap_test(void) {\n    // TODO: allocate, reallocate deallocate, create, dispose\n    void*   a[1024]; // addresses\n    int32_t b[1024]; // bytes\n    uint32_t seed = 0x1;\n    for (int i = 0; i < 1024; i++) {\n        b[i] = (int32_t)(rt_num.random32(&seed) % 1024) + 1;\n        errno_t r = rt_heap.alloc(&a[i], b[i]);\n        rt_swear(r == 0);\n    }\n    for (int i = 0; i < 1024; i++) {\n        rt_heap.free(a[i]);\n    }\n    HeapCompact(rt_heap_or_process_heap(null), 0);\n    // \"There is no extended error information for HeapValidate;\n    //  do not call GetLastError.\"\n    rt_swear(HeapValidate(rt_heap_or_process_heap(null), 0, null));\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_heap_test(void) { }\n\n#endif\n\nrt_heap_if rt_heap = {\n    .alloc        = rt_heap_alloc,\n    .alloc_zero   = rt_heap_alloc_zero,\n    .realloc      = rt_heap_realloc,\n    .realloc_zero = rt_heap_realloc_zero,\n    .free         = rt_heap_free,\n    .create       = rt_heap_create,\n    .allocate     = rt_heap_allocate,\n    .reallocate   = rt_heap_reallocate,\n    .deallocate   = rt_heap_deallocate,\n    .bytes        = rt_heap_bytes,\n    .dispose      = rt_heap_dispose,\n    .test         = rt_heap_test\n};\n"
  },
  {
    "path": "src/rt/rt_loader.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// This is oversimplified Win32 version completely ignoring mode.\n\n// I bit more Posix compliant version is here:\n// https://github.com/dlfcn-win32/dlfcn-win32/blob/master/src/dlfcn.c\n// POSIX says that if the value of file is NULL, a handle on a global\n// symbol object must be provided. That object must be able to access\n// all symbols from the original program file, and any objects loaded\n// with the RTLD_GLOBAL flag.\n// The return value from GetModuleHandle( ) allows us to retrieve\n// symbols only from the original program file. EnumProcessModules() is\n// used to access symbols from other libraries. For objects loaded\n// with the RTLD_LOCAL flag, we create our own list later on. They are\n// excluded from EnumProcessModules() iteration.\n\nstatic void* rt_loader_all;\n\nstatic void* rt_loader_sym_all(const char* name) {\n    void* sym = null;\n    DWORD bytes = 0;\n    rt_fatal_win32err(EnumProcessModules(GetCurrentProcess(),\n                                         null, 0, &bytes));\n    rt_assert(bytes % sizeof(HMODULE) == 0);\n    rt_assert(bytes / sizeof(HMODULE) < 1024); // OK to allocate 8KB on stack\n    HMODULE* modules = null;\n    rt_fatal_if_error(rt_heap.allocate(null, (void**)&modules, bytes, false));\n    rt_fatal_win32err(EnumProcessModules(GetCurrentProcess(),\n                                         modules, bytes, &bytes));\n    const int32_t n = bytes / (int32_t)sizeof(HMODULE);\n    for (int32_t i = 0; i < n && sym != null; i++) {\n        sym = rt_loader.sym(modules[i], name);\n    }\n    if (sym == null) {\n        sym = rt_loader.sym(GetModuleHandleA(null), name);\n    }\n    rt_heap.deallocate(null, modules);\n    return sym;\n}\n\nstatic void* rt_loader_open(const char* filename, int32_t rt_unused(mode)) {\n    return filename == null ? &rt_loader_all : (void*)LoadLibraryA(filename);\n}\n\nstatic void* rt_loader_sym(void* handle, const char* name) {\n    return handle == &rt_loader_all ?\n            (void*)rt_loader_sym_all(name) :\n            (void*)GetProcAddress((HMODULE)handle, name);\n}\n\nstatic void rt_loader_close(void* handle) {\n    if (handle != &rt_loader_all) {\n        rt_fatal_win32err(FreeLibrary(handle));\n    }\n}\n\n#ifdef RT_TESTS\n\n// manually test exported function once and comment out because of\n// creating .lib out of each .exe is annoying\n\n#undef RT_LOADER_TEST_EXPORTED_FUNCTION\n\n#ifdef RT_LOADER_TEST_EXPORTED_FUNCTION\n\n\nstatic int32_t rt_loader_test_calls_count;\n\nrt_export void rt_loader_test_exported_function(void);\n\nvoid rt_loader_test_exported_function(void) { rt_loader_test_calls_count++; }\n\n#endif\n\nstatic void rt_loader_test(void) {\n    void* global = rt_loader.open(null, rt_loader.local);\n    rt_loader.close(global);\n    // NtQueryTimerResolution - http://undocumented.ntinternals.net/\n    typedef long (__stdcall *query_timer_resolution_t)(\n        long* minimum_resolution,\n        long* maximum_resolution,\n        long* current_resolution);\n    void* nt_dll = rt_loader.open(\"ntdll\", rt_loader.local);\n    query_timer_resolution_t query_timer_resolution =\n        (query_timer_resolution_t)rt_loader.sym(nt_dll, \"NtQueryTimerResolution\");\n    // in 100ns = 0.1us units\n    long min_resolution = 0;\n    long max_resolution = 0; // lowest possible delay between timer events\n    long cur_resolution = 0;\n    rt_fatal_if(query_timer_resolution(\n        &min_resolution, &max_resolution, &cur_resolution) != 0);\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f millisecond\",\n//          min_resolution / 10.0 / 1000.0,\n//          max_resolution / 10.0 / 1000.0,\n//          cur_resolution / 10.0 / 1000.0);\n//      // Interesting observation cur_resolution sometimes 15.625ms or 1.0ms\n//  }\n    rt_loader.close(nt_dll);\n#ifdef RT_LOADER_TEST_EXPORTED_FUNCTION\n    rt_loader_test_calls_count = 0;\n    rt_loader_test_exported_function(); // to make sure it is linked in\n    rt_swear(rt_loader_test_calls_count == 1);\n    typedef void (*foo_t)(void);\n    foo_t foo = (foo_t)rt_loader.sym(global, \"rt_loader_test_exported_function\");\n    foo();\n    rt_swear(rt_loader_test_calls_count == 2);\n#endif\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_loader_test(void) {}\n\n#endif\n\nenum {\n    rt_loader_local  = 0,       // RTLD_LOCAL  All symbols are not made available for relocation processing by other modules.\n    rt_loader_lazy   = 1,       // RTLD_LAZY   Relocations are performed at an implementation-dependent time.\n    rt_loader_now    = 2,       // RTLD_NOW    Relocations are performed when the object is loaded.\n    rt_loader_global = 0x00100, // RTLD_GLOBAL All symbols are available for relocation processing of other modules.\n};\n\nrt_loader_if rt_loader = {\n    .local  = rt_loader_local,\n    .lazy   = rt_loader_lazy,\n    .now    = rt_loader_now,\n    .global = rt_loader_global,\n    .open   = rt_loader_open,\n    .sym    = rt_loader_sym,\n    .close  = rt_loader_close,\n    .test   = rt_loader_test\n};\n"
  },
  {
    "path": "src/rt/rt_mem.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic errno_t rt_mem_map_view_of_file(HANDLE file,\n        void* *data, int64_t *bytes, bool rw) {\n    errno_t r = 0;\n    void* address = null;\n    HANDLE mapping = CreateFileMapping(file, null,\n        rw ? PAGE_READWRITE : PAGE_READONLY,\n        (uint32_t)(*bytes >> 32), (uint32_t)*bytes, null);\n    if (mapping == null) {\n        r = rt_core.err();\n    } else {\n        DWORD access = rw ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ;\n        address = MapViewOfFile(mapping, access, 0, 0, (SIZE_T)*bytes);\n        if (address == null) { r = rt_core.err(); }\n        rt_win32_close_handle(mapping);\n    }\n    if (r == 0) {\n        *data = address;\n    } else {\n        *data = null;\n        *bytes = 0;\n    }\n    return r;\n}\n\n// see: https://learn.microsoft.com/en-us/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--\n\nstatic errno_t rt_mem_set_token_privilege(void* token,\n            const char* name, bool e) {\n    TOKEN_PRIVILEGES tp = { .PrivilegeCount = 1 };\n    tp.Privileges[0].Attributes = e ? SE_PRIVILEGE_ENABLED : 0;\n    rt_fatal_win32err(LookupPrivilegeValueA(null, name, &tp.Privileges[0].Luid));\n    return rt_b2e(AdjustTokenPrivileges(token, false, &tp,\n               sizeof(TOKEN_PRIVILEGES), null, null));\n}\n\nstatic errno_t rt_mem_adjust_process_privilege_manage_volume_name(void) {\n    // see: https://devblogs.microsoft.com/oldnewthing/20160603-00/?p=93565\n    const uint32_t access = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;\n    const HANDLE process = GetCurrentProcess();\n    HANDLE token = null;\n    errno_t r = rt_b2e(OpenProcessToken(process, access, &token));\n    if (r == 0) {\n        const char* se_manage_volume_name = \"SeManageVolumePrivilege\";\n        r = rt_mem_set_token_privilege(token, se_manage_volume_name, true);\n        rt_win32_close_handle(token);\n    }\n    return r;\n}\n\nstatic errno_t rt_mem_map_file(const char* filename, void* *data,\n        int64_t *bytes, bool rw) {\n    if (rw) { // for SetFileValidData() call:\n        (void)rt_mem_adjust_process_privilege_manage_volume_name();\n    }\n    errno_t r = 0;\n    const DWORD access = GENERIC_READ | (rw ? GENERIC_WRITE : 0);\n    const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    const DWORD disposition = rw ? OPEN_ALWAYS : OPEN_EXISTING;\n    const DWORD flags = FILE_ATTRIBUTE_NORMAL;\n    HANDLE file = CreateFileA(filename, access, share, null, disposition,\n                              flags, null);\n    if (file == INVALID_HANDLE_VALUE) {\n        r = rt_core.err();\n    } else {\n        LARGE_INTEGER eof = { .QuadPart = 0 };\n        rt_fatal_win32err(GetFileSizeEx(file, &eof));\n        if (rw && *bytes > eof.QuadPart) { // increase file size\n            const LARGE_INTEGER size = { .QuadPart = *bytes };\n            r = r != 0 ? r : (rt_b2e(SetFilePointerEx(file, size, null, FILE_BEGIN)));\n            r = r != 0 ? r : (rt_b2e(SetEndOfFile(file)));\n            // the following not guaranteed to work but helps with sparse files\n            r = r != 0 ? r : (rt_b2e(SetFileValidData(file, *bytes)));\n            // SetFileValidData() only works for Admin (verified) or System accounts\n            if (r == ERROR_PRIVILEGE_NOT_HELD) { r = 0; } // ignore\n            // SetFileValidData() is also semi-security hole because it allows to read\n            // previously not zeroed disk content of other files\n            const LARGE_INTEGER zero = { .QuadPart = 0 }; // rewind stream:\n            r = r != 0 ? r : (rt_b2e(SetFilePointerEx(file, zero, null, FILE_BEGIN)));\n        } else {\n            *bytes = eof.QuadPart;\n        }\n        r = r != 0 ? r : rt_mem_map_view_of_file(file, data, bytes, rw);\n        rt_win32_close_handle(file);\n    }\n    return r;\n}\n\nstatic errno_t rt_mem_map_ro(const char* filename, void* *data, int64_t *bytes) {\n    return rt_mem_map_file(filename, data, bytes, false);\n}\n\nstatic errno_t rt_mem_map_rw(const char* filename, void* *data, int64_t *bytes) {\n    return rt_mem_map_file(filename, data, bytes, true);\n}\n\nstatic void rt_mem_unmap(void* data, int64_t bytes) {\n    rt_assert(data != null && bytes > 0);\n    (void)bytes; /* unused only need for posix version */\n    if (data != null && bytes > 0) {\n        rt_fatal_win32err(UnmapViewOfFile(data));\n    }\n}\n\nstatic errno_t rt_mem_map_resource(const char* label, void* *data, int64_t *bytes) {\n    HRSRC res = FindResourceA(null, label, (const char*)RT_RCDATA);\n    // \"LockResource does not actually lock memory; it is just used to\n    // obtain a pointer to the memory containing the resource data.\n    // The name of the function comes from versions prior to Windows XP,\n    // when it was used to lock a global memory block allocated by LoadResource.\"\n    if (res != null) { *bytes = SizeofResource(null, res); }\n    HGLOBAL g = res != null ? LoadResource(null, res) : null;\n    *data = g != null ? LockResource(g) : null;\n    return *data != null ? 0 : rt_core.err();\n}\n\nstatic int32_t rt_mem_page_size(void) {\n    static SYSTEM_INFO system_info;\n    if (system_info.dwPageSize == 0) {\n        GetSystemInfo(&system_info);\n    }\n    return (int32_t)system_info.dwPageSize;\n}\n\nstatic int rt_mem_large_page_size(void) {\n    static SIZE_T large_page_minimum = 0;\n    if (large_page_minimum == 0) {\n        large_page_minimum = GetLargePageMinimum();\n    }\n    return (int32_t)large_page_minimum;\n}\n\nstatic void* rt_mem_allocate(int64_t bytes_multiple_of_page_size) {\n    rt_assert(bytes_multiple_of_page_size > 0);\n    SIZE_T bytes = (SIZE_T)bytes_multiple_of_page_size;\n    SIZE_T page_size = (SIZE_T)rt_mem_page_size();\n    rt_assert(bytes % page_size == 0);\n    errno_t r = 0;\n    void* a = null;\n    if (bytes_multiple_of_page_size < 0 || bytes % page_size != 0) {\n        SetLastError(ERROR_INVALID_PARAMETER);\n        r = EINVAL;\n    } else {\n        const DWORD type = MEM_COMMIT | MEM_RESERVE;\n        const DWORD physical = type | MEM_PHYSICAL;\n        a = VirtualAlloc(null, bytes, physical, PAGE_READWRITE);\n        if (a == null) {\n            a = VirtualAlloc(null, bytes, type, PAGE_READWRITE);\n        }\n        if (a == null) {\n            r = rt_core.err();\n            if (r != 0) {\n                rt_println(\"VirtualAlloc(%lld) failed %s\", bytes, rt_strerr(r));\n            }\n        } else {\n            r = VirtualLock(a, bytes) ? 0 : rt_core.err();\n            if (r == ERROR_WORKING_SET_QUOTA) {\n                // The default size is 345 pages (for example,\n                // this is 1,413,120 bytes on systems with a 4K page size).\n                SIZE_T min_mem = 0, max_mem = 0;\n                r = rt_b2e(GetProcessWorkingSetSize(GetCurrentProcess(), &min_mem, &max_mem));\n                if (r != 0) {\n                    rt_println(\"GetProcessWorkingSetSize() failed %s\", rt_strerr(r));\n                } else {\n                    max_mem =  max_mem + bytes * 2LL;\n                    max_mem = (max_mem + page_size - 1) / page_size * page_size +\n                               page_size * 16;\n                    if (min_mem < max_mem) { min_mem = max_mem; }\n                    r = rt_b2e(SetProcessWorkingSetSize(GetCurrentProcess(),\n                            min_mem, max_mem));\n                    if (r != 0) {\n                        rt_println(\"SetProcessWorkingSetSize(%lld, %lld) failed %s\",\n                            (uint64_t)min_mem, (uint64_t)max_mem, rt_strerr(r));\n                    } else {\n                        r = rt_b2e(VirtualLock(a, bytes));\n                    }\n                }\n            }\n            if (r != 0) {\n                rt_println(\"VirtualLock(%lld) failed %s\", bytes, rt_strerr(r));\n            }\n        }\n    }\n    if (r != 0) {\n        rt_println(\"mem_alloc_pages(%lld) failed %s\", bytes, rt_strerr(r));\n        rt_assert(a == null);\n    }\n    return a;\n}\n\nstatic void rt_mem_deallocate(void* a, int64_t bytes_multiple_of_page_size) {\n    rt_assert(bytes_multiple_of_page_size > 0);\n    SIZE_T bytes = (SIZE_T)bytes_multiple_of_page_size;\n    errno_t r = 0;\n    SIZE_T page_size = (SIZE_T)rt_mem_page_size();\n    if (bytes_multiple_of_page_size < 0 || bytes % page_size != 0) {\n        r = EINVAL;\n        rt_println(\"failed %s\", rt_strerr(r));\n    } else {\n        if (a != null) {\n            // in case it was successfully locked\n            r = rt_b2e(VirtualUnlock(a, bytes));\n            if (r != 0) {\n                rt_println(\"VirtualUnlock() failed %s\", rt_strerr(r));\n            }\n            // If the \"dwFreeType\" parameter is MEM_RELEASE, \"dwSize\" parameter\n            // must be the base address returned by the VirtualAlloc function when\n            // the region of pages is reserved.\n            r = rt_b2e(VirtualFree(a, 0, MEM_RELEASE));\n            if (r != 0) { rt_println(\"VirtuaFree() failed %s\", rt_strerr(r)); }\n        }\n    }\n}\n\nstatic void rt_mem_test(void) {\n    #ifdef RT_TESTS\n    rt_swear(rt_args.c > 0);\n    void* data = null;\n    int64_t bytes = 0;\n    rt_swear(rt_mem.map_ro(rt_args.v[0], &data, &bytes) == 0);\n    rt_swear(data != null && bytes != 0);\n    rt_mem.unmap(data, bytes);\n    // TODO: page_size large_page_size allocate deallocate\n    // TODO: test heap functions\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_mem_if rt_mem = {\n    .map_ro          = rt_mem_map_ro,\n    .map_rw          = rt_mem_map_rw,\n    .unmap           = rt_mem_unmap,\n    .map_resource    = rt_mem_map_resource,\n    .page_size       = rt_mem_page_size,\n    .large_page_size = rt_mem_large_page_size,\n    .allocate        = rt_mem_allocate,\n    .deallocate      = rt_mem_deallocate,\n    .test            = rt_mem_test\n};\n"
  },
  {
    "path": "src/rt/rt_nls.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// Simplistic Win32 implementation of national language support.\n// Windows NLS family of functions is very complicated and has\n// difficult history of LANGID vs LCID etc... See:\n// ResolveLocaleName()\n// GetThreadLocale()\n// SetThreadLocale()\n// GetUserDefaultLocaleName()\n// WM_SETTINGCHANGE lParam=\"intl\"\n// and many others...\n\nenum {\n    rt_nls_str_count_max = 1024,\n    rt_nls_str_mem_max = 64 * rt_nls_str_count_max\n};\n\nstatic char  rt_nls_strings_memory[rt_nls_str_mem_max]; // increase if overflows\nstatic char* rt_nls_strings_free = rt_nls_strings_memory;\n\nstatic int32_t rt_nls_strings_count;\n\nstatic const char* rt_nls_ls[rt_nls_str_count_max]; // localized strings\nstatic const char* rt_nls_ns[rt_nls_str_count_max]; // neutral language strings\n\nstatic uint16_t* rt_nls_load_string(int32_t strid, LANGID lang_id) {\n    rt_assert(0 <= strid && strid < rt_countof(rt_nls_ns));\n    uint16_t* r = null;\n    int32_t block = strid / 16 + 1;\n    int32_t index  = strid % 16;\n    HRSRC res = FindResourceExW(((HMODULE)null), RT_STRING,\n        MAKEINTRESOURCEW(block), lang_id);\n//  rt_println(\"FindResourceExA(block=%d lang_id=%04X)=%p\", block, lang_id, res);\n    uint8_t* memory = res == null ? null : (uint8_t*)LoadResource(null, res);\n    uint16_t* ws = memory == null ? null : (uint16_t*)LockResource(memory);\n//  rt_println(\"LockResource(block=%d lang_id=%04X)=%p\", block, lang_id, ws);\n    if (ws != null) {\n        for (int32_t i = 0; i < 16 && r == null; i++) {\n            if (ws[0] != 0) {\n                int32_t count = (int32_t)ws[0];  // String size in characters.\n                ws++;\n                rt_assert(ws[count - 1] == 0, \"use rc.exe /n command line option\");\n                if (i == index) { // the string has been found\n//                  rt_println(\"%04X found %s\", lang_id, utf16to8(ws));\n                    r = ws;\n                }\n                ws += count;\n            } else {\n                ws++;\n            }\n        }\n    }\n    return r;\n}\n\nstatic const char* rt_nls_save_string(uint16_t* utf16) {\n    const int32_t bytes = rt_str.utf8_bytes(utf16, -1);\n    rt_swear(bytes > 1);\n    char* s = rt_nls_strings_free;\n    uintptr_t left = (uintptr_t)rt_countof(rt_nls_strings_memory) -\n        (uintptr_t)(rt_nls_strings_free - rt_nls_strings_memory);\n    rt_fatal_if(left < (uintptr_t)bytes, \"string_memory[] overflow\");\n    rt_str.utf16to8(s, (int32_t)left, utf16, -1);\n    rt_assert((int32_t)strlen(s) == bytes - 1, \"utf16to8() does not truncate\");\n    rt_nls_strings_free += bytes;\n    return s;\n}\n\nstatic const char* rt_nls_localized_string(int32_t strid) {\n    rt_swear(0 < strid && strid < rt_countof(rt_nls_ns));\n    const char* s = null;\n    if (0 < strid && strid < rt_countof(rt_nls_ns)) {\n        if (rt_nls_ls[strid] != null) {\n            s = rt_nls_ls[strid];\n        } else {\n            LCID lc_id = GetThreadLocale();\n            LANGID lang_id = LANGIDFROMLCID(lc_id);\n            uint16_t* utf16 = rt_nls_load_string(strid, lang_id);\n            if (utf16 == null) { // try default dialect:\n                LANGID primary = PRIMARYLANGID(lang_id);\n                lang_id = MAKELANGID(primary, SUBLANG_NEUTRAL);\n                utf16 = rt_nls_load_string(strid, lang_id);\n            }\n            if (utf16 != null && utf16[0] != 0x0000) {\n                s = rt_nls_save_string(utf16);\n                rt_nls_ls[strid] = s;\n            }\n        }\n    }\n    return s;\n}\n\nstatic int32_t rt_nls_strid(const char* s) {\n    int32_t strid = -1;\n    for (int32_t i = 1; i < rt_nls_strings_count && strid == -1; i++) {\n        if (rt_nls_ns[i] != null && strcmp(s, rt_nls_ns[i]) == 0) {\n            strid = i;\n            rt_nls_localized_string(strid); // to save it, ignore result\n        }\n    }\n    return strid;\n}\n\nstatic const char* rt_nls_string(int32_t strid, const char* defau1t) {\n    const char* r = rt_nls_localized_string(strid);\n    return r == null ? defau1t : r;\n}\n\nstatic const char* rt_nls_str(const char* s) {\n    int32_t id = rt_nls_strid(s);\n    return id < 0 ? s : rt_nls_string(id, s);\n}\n\nstatic const char* rt_nls_locale(void) {\n    uint16_t utf16[LOCALE_NAME_MAX_LENGTH + 1];\n    LCID lc_id = GetThreadLocale();\n    int32_t n = LCIDToLocaleName(lc_id, utf16, rt_countof(utf16),\n        LOCALE_ALLOW_NEUTRAL_NAMES);\n    static char ln[LOCALE_NAME_MAX_LENGTH * 4 + 1];\n    ln[0] = 0;\n    if (n == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"LCIDToLocaleName(0x%04X) failed %s\", lc_id, rt_str.error(r));\n    } else {\n        rt_str.utf16to8(ln, rt_countof(ln), utf16, -1);\n    }\n    return ln;\n}\n\nstatic errno_t rt_nls_set_locale(const char* locale) {\n    errno_t r = 0;\n    uint16_t utf16[LOCALE_NAME_MAX_LENGTH + 1];\n    rt_str.utf8to16(utf16, rt_countof(utf16), locale, -1);\n    uint16_t rln[LOCALE_NAME_MAX_LENGTH + 1]; // resolved locale name\n    int32_t n = (int32_t)ResolveLocaleName(utf16, rln, (DWORD)rt_countof(rln));\n    if (n == 0) {\n        r = rt_core.err();\n        rt_println(\"ResolveLocaleName(\\\"%s\\\") failed %s\", locale, rt_str.error(r));\n    } else {\n        LCID lc_id = LocaleNameToLCID(rln, LOCALE_ALLOW_NEUTRAL_NAMES);\n        if (lc_id == 0) {\n            r = rt_core.err();\n            rt_println(\"LocaleNameToLCID(\\\"%s\\\") failed %s\", locale, rt_str.error(r));\n        } else {\n            rt_fatal_win32err(SetThreadLocale(lc_id));\n            memset((void*)rt_nls_ls, 0, sizeof(rt_nls_ls)); // start all over\n        }\n    }\n    return r;\n}\n\nstatic void rt_nls_init(void) {\n    static_assert(rt_countof(rt_nls_ns) % 16 == 0, \n                 \"rt_countof(ns) must be multiple of 16\");\n    LANGID lang_id = MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL);\n    for (int32_t strid = 0; strid < rt_countof(rt_nls_ns); strid += 16) {\n        int32_t block = strid / 16 + 1;\n        HRSRC res = FindResourceExW(((HMODULE)null), RT_STRING,\n            MAKEINTRESOURCEW(block), lang_id);\n        uint8_t* memory = res == null ? null : (uint8_t*)LoadResource(null, res);\n        uint16_t* ws = memory == null ? null : (uint16_t*)LockResource(memory);\n        if (ws == null) { break; }\n        for (int32_t i = 0; i < 16; i++) {\n            int32_t ix = strid + i;\n            uint16_t count = ws[0];\n            if (count > 0) {\n                ws++;\n                rt_fatal_if(ws[count - 1] != 0, \"use rc.exe /n\");\n                rt_nls_ns[ix] = rt_nls_save_string(ws);\n                rt_nls_strings_count = ix + 1;\n//              rt_println(\"ns[%d] := %d \\\"%s\\\"\", ix, strlen(rt_nls_ns[ix]), rt_nls_ns[ix]);\n                ws += count;\n            } else {\n                ws++;\n            }\n        }\n    }\n}\n\nrt_nls_if rt_nls = {\n    .init       = rt_nls_init,\n    .strid      = rt_nls_strid,\n    .str        = rt_nls_str,\n    .string     = rt_nls_string,\n    .locale     = rt_nls_locale,\n    .set_locale = rt_nls_set_locale,\n};\n"
  },
  {
    "path": "src/rt/rt_num.c",
    "content": "#include \"rt/rt.h\"\n#include <intrin.h>\n//#include <immintrin.h> // _tzcnt_u32\n\nstatic inline rt_num128_t rt_num_add128_inline(const rt_num128_t a, const rt_num128_t b) {\n    rt_num128_t r = a;\n    r.hi += b.hi;\n    r.lo += b.lo;\n    if (r.lo < b.lo) { r.hi++; } // carry\n    return r;\n}\n\nstatic inline rt_num128_t rt_num_sub128_inline(const rt_num128_t a, const rt_num128_t b) {\n    rt_num128_t r = a;\n    r.hi -= b.hi;\n    if (r.lo < b.lo) { r.hi--; } // borrow\n    r.lo -= b.lo;\n    return r;\n}\n\nstatic rt_num128_t rt_num_add128(const rt_num128_t a, const rt_num128_t b) {\n    return rt_num_add128_inline(a, b);\n}\n\nstatic rt_num128_t rt_num_sub128(const rt_num128_t a, const rt_num128_t b) {\n    return rt_num_sub128_inline(a, b);\n}\n\nstatic rt_num128_t rt_num_mul64x64(uint64_t a, uint64_t b) {\n    uint64_t a_lo = (uint32_t)a;\n    uint64_t a_hi = a >> 32;\n    uint64_t b_lo = (uint32_t)b;\n    uint64_t b_hi = b >> 32;\n    uint64_t low = a_lo * b_lo;\n    uint64_t cross1 = a_hi * b_lo;\n    uint64_t cross2 = a_lo * b_hi;\n    uint64_t high = a_hi * b_hi;\n    // this cannot overflow as (2^32-1)^2 + 2^32-1 < 2^64-1\n    cross1 += low >> 32;\n    // this one can overflow\n    cross1 += cross2;\n    // propagate the carry if any\n    high += ((uint64_t)(cross1 < cross2 != 0)) << 32;\n    high = high + (cross1 >> 32);\n    low = ((cross1 & 0xFFFFFFFF) << 32) + (low & 0xFFFFFFFF);\n    return (rt_num128_t){.lo = low, .hi = high };\n}\n\nstatic inline void rt_num_shift128_left_inline(rt_num128_t* n) {\n    const uint64_t top = (1ULL << 63);\n    n->hi = (n->hi << 1) | ((n->lo & top) ? 1 : 0);\n    n->lo = (n->lo << 1);\n}\n\nstatic inline void rt_num_shift128_right_inline(rt_num128_t* n) {\n    const uint64_t top = (1ULL << 63);\n    n->lo = (n->lo >> 1) | ((n->hi & 0x1) ? top : 0);\n    n->hi = (n->hi >> 1);\n}\n\nstatic inline bool rt_num_less128_inline(const rt_num128_t a, const rt_num128_t b) {\n    return a.hi < b.hi || (a.hi == b.hi && a.lo < b.lo);\n}\n\nstatic inline bool rt_num_uint128_high_bit(const rt_num128_t a) {\n    return (int64_t)a.hi < 0;\n}\n\nstatic uint64_t rt_num_muldiv128(uint64_t a, uint64_t b, uint64_t divisor) {\n    rt_swear(divisor > 0, \"divisor: %lld\", divisor);\n    rt_num128_t r = rt_num.mul64x64(a, b); // reminder: a * b\n    uint64_t q = 0; // quotient\n    if (r.hi >= divisor) {\n        q = UINT64_MAX; // overflow\n    } else {\n        int32_t  shift = 0;\n        rt_num128_t d = { .hi = 0, .lo = divisor };\n        while (!rt_num_uint128_high_bit(d) && rt_num_less128_inline(d, r)) {\n            rt_num_shift128_left_inline(&d);\n            shift++;\n        }\n        rt_assert(shift <= 64);\n        while (shift >= 0 && (d.hi != 0 || d.lo != 0)) {\n            if (!rt_num_less128_inline(r, d)) {\n                r = rt_num_sub128_inline(r, d);\n                rt_assert(shift < 64);\n                q |= (1ULL << shift);\n            }\n            rt_num_shift128_right_inline(&d);\n            shift--;\n        }\n    }\n    return q;\n}\n\nstatic uint32_t rt_num_gcd32(uint32_t u, uint32_t v) {\n    #pragma push_macro(\"rt_trailing_zeros\")\n    #ifdef _M_ARM64\n    #define rt_trailing_zeros(x) (_CountTrailingZeros(x))\n    #else\n    #define rt_trailing_zeros(x) ((int32_t)_tzcnt_u32(x))\n    #endif\n    if (u == 0) {\n        return v;\n    } else if (v == 0) {\n        return u;\n    }\n    uint32_t i = rt_trailing_zeros(u);  u >>= i;\n    uint32_t j = rt_trailing_zeros(v);  v >>= j;\n    uint32_t k = rt_min(i, j);\n    for (;;) {\n        rt_assert(u % 2 == 1, \"u = %d should be odd\", u);\n        rt_assert(v % 2 == 1, \"v = %d should be odd\", v);\n        if (u > v) { uint32_t swap = u; u = v; v = swap; }\n        v -= u;\n        if (v == 0) { return u << k; }\n        v >>= rt_trailing_zeros(v);\n    }\n    #pragma pop_macro(\"rt_trailing_zeros\")\n}\n\nstatic uint32_t rt_num_random32(uint32_t* state) {\n    // https://gist.github.com/tommyettinger/46a874533244883189143505d203312c\n    static rt_thread_local bool started; // first seed must be odd\n    if (!started) { started = true; *state |= 1; }\n    uint32_t z = (*state += 0x6D2B79F5UL);\n    z = (z ^ (z >> 15)) * (z | 1UL);\n    z ^= z + (z ^ (z >> 7)) * (z | 61UL);\n    return z ^ (z >> 14);\n}\n\nstatic uint64_t rt_num_random64(uint64_t *state) {\n    // https://gist.github.com/tommyettinger/e6d3e8816da79b45bfe582384c2fe14a\n    static rt_thread_local bool started; // first seed must be odd\n    if (!started) { started = true; *state |= 1; }\n\tconst uint64_t s = *state;\n\tconst uint64_t z = (s ^ s >> 25) * (*state += 0x6A5D39EAE12657AAULL);\n\treturn z ^ (z >> 22);\n}\n\n// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\n\nstatic uint32_t rt_num_hash32(const char *data, int64_t len) {\n    uint32_t hash  = 0x811c9dc5;  // FNV_offset_basis for 32-bit\n    uint32_t prime = 0x01000193; // FNV_prime for 32-bit\n    if (len > 0) {\n        for (int64_t i = 1; i < len; i++) {\n            hash ^= (uint32_t)data[i];\n            hash *= prime;\n        }\n    } else {\n        for (int64_t i = 0; data[i] != 0; i++) {\n            hash ^= (uint32_t)data[i];\n            hash *= prime;\n        }\n    }\n    return hash;\n}\n\nstatic uint64_t rt_num_hash64(const char *data, int64_t len) {\n    uint64_t hash  = 0xcbf29ce484222325; // FNV_offset_basis for 64-bit\n    uint64_t prime = 0x100000001b3;      // FNV_prime for 64-bit\n    if (len > 0) {\n        for (int64_t i = 0; i < len; i++) {\n            hash ^= (uint64_t)data[i];\n            hash *= prime;\n        }\n    } else {\n        for (int64_t i = 0; data[i] != 0; i++) {\n            hash ^= (uint64_t)data[i];\n            hash *= prime;\n        }\n    }\n    return hash;\n}\n\nstatic uint32_t ctz_2(uint32_t x) {\n    if (x == 0) return 32;\n    unsigned n = 0;\n    while ((x & 1) == 0) {\n        x >>= 1;\n        n++;\n    }\n    return n;\n}\n\nstatic void rt_num_test(void) {\n    #ifdef RT_TESTS\n    {\n        rt_swear(rt_num.gcd32(1000000000, 24000000) == 8000000);\n        // https://asecuritysite.com/encryption/nprimes?y=64\n        // https://www.rapidtables.com/convert/number/decimal-to-hex.html\n        uint64_t p = 15843490434539008357u; // prime\n        uint64_t q = 16304766625841520833u; // prime\n        // pq: 258324414073910997987910483408576601381\n        //     0xC25778F20853A9A1EC0C27C467C45D25\n        rt_num128_t pq = {.hi = 0xC25778F20853A9A1uLL,\n                       .lo = 0xEC0C27C467C45D25uLL };\n        rt_num128_t p_q = rt_num.mul64x64(p, q);\n        rt_swear(p_q.hi == pq.hi && pq.lo == pq.lo);\n        uint64_t p1 = rt_num.muldiv128(p, q, q);\n        uint64_t q1 = rt_num.muldiv128(p, q, p);\n        rt_swear(p1 == p);\n        rt_swear(q1 == q);\n    }\n    #ifdef DEBUG\n    enum { n = 100 };\n    #else\n    enum { n = 10000 };\n    #endif\n    uint64_t seed64 = 1;\n    for (int32_t i = 0; i < n; i++) {\n        uint64_t p = rt_num.random64(&seed64);\n        uint64_t q = rt_num.random64(&seed64);\n        uint64_t p1 = rt_num.muldiv128(p, q, q);\n        uint64_t q1 = rt_num.muldiv128(p, q, p);\n        rt_swear(p == p1, \"0%16llx (0%16llu) != 0%16llx (0%16llu)\", p, p1);\n        rt_swear(q == q1, \"0%16llx (0%16llu) != 0%16llx (0%16llu)\", p, p1);\n    }\n    uint32_t seed32 = 1;\n    for (int32_t i = 0; i < n; i++) {\n        uint64_t p = rt_num.random32(&seed32);\n        uint64_t q = rt_num.random32(&seed32);\n        uint64_t r = rt_num.muldiv128(p, q, 1);\n        rt_swear(r == p * q);\n        // division by the maximum uint64_t value:\n        r = rt_num.muldiv128(p, q, UINT64_MAX);\n        rt_swear(r == 0);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n    #endif\n}\n\nrt_num_if rt_num = {\n    .add128    = rt_num_add128,\n    .sub128    = rt_num_sub128,\n    .mul64x64  = rt_num_mul64x64,\n    .muldiv128 = rt_num_muldiv128,\n    .gcd32     = rt_num_gcd32,\n    .random32  = rt_num_random32,\n    .random64  = rt_num_random64,\n    .hash32    = rt_num_hash32,\n    .hash64    = rt_num_hash64,\n    .test      = rt_num_test\n};\n"
  },
  {
    "path": "src/rt/rt_processes.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\ntypedef struct rt_processes_pidof_lambda_s rt_processes_pidof_lambda_t;\n\ntypedef struct rt_processes_pidof_lambda_s {\n    bool (*each)(rt_processes_pidof_lambda_t* p, uint64_t pid); // returns true to continue\n    uint64_t* pids;\n    size_t size;  // pids[size]\n    size_t count; // number of valid pids in the pids\n    fp64_t timeout;\n    errno_t error;\n} rt_processes_pidof_lambda_t;\n\nstatic int32_t rt_processes_for_each_pidof(const char* pname, rt_processes_pidof_lambda_t* la) {\n    char stack[1024]; // avoid alloca()\n    int32_t n = rt_str.len(pname);\n    rt_fatal_if(n + 5 >= rt_countof(stack), \"name is too long: %s\", pname);\n    const char* name = pname;\n    // append \".exe\" if not present:\n    if (!rt_str.iends(pname, \".exe\")) {\n        int32_t k = (int32_t)strlen(pname) + 5;\n        char* exe = stack;\n        rt_str.format(exe, k, \"%s.exe\", pname);\n        name = exe;\n    }\n    const char* base = strrchr(name, '\\\\');\n    if (base != null) {\n        base++; // advance past \"\\\\\"\n    } else {\n        base = name;\n    }\n    uint16_t wn[1024];\n    rt_fatal_if(strlen(base) >= rt_countof(wn), \"name too long: %s\", base);\n    rt_str.utf8to16(wn, rt_countof(wn), base, -1);\n    size_t count = 0;\n    uint64_t pid = 0;\n    uint8_t* data = null;\n    ULONG bytes = 0;\n    errno_t r = NtQuerySystemInformation(SystemProcessInformation, data, 0, &bytes);\n    #pragma push_macro(\"STATUS_INFO_LENGTH_MISMATCH\")\n    #define STATUS_INFO_LENGTH_MISMATCH      0xC0000004\n    while (r == (errno_t)STATUS_INFO_LENGTH_MISMATCH) {\n        // bytes == 420768 on Windows 11 which may be a bit\n        // too much for stack alloca()\n        // add little extra if new process is spawned in between calls.\n        bytes += sizeof(SYSTEM_PROCESS_INFORMATION) * 32;\n        r = rt_heap.reallocate(null, (void**)&data, bytes, false);\n        if (r == 0) {\n            r = NtQuerySystemInformation(SystemProcessInformation, data, bytes, &bytes);\n        } else {\n            rt_assert(r == (errno_t)ERROR_NOT_ENOUGH_MEMORY);\n        }\n    }\n    #pragma pop_macro(\"STATUS_INFO_LENGTH_MISMATCH\")\n    if (r == 0 && data != null) {\n        SYSTEM_PROCESS_INFORMATION* proc = (SYSTEM_PROCESS_INFORMATION*)data;\n        while (proc != null) {\n            uint16_t* img = proc->ImageName.Buffer; // last name only, not a pathname!\n            bool match = img != null && wcsicmp(img, wn) == 0;\n            if (match) {\n                pid = (uint64_t)proc->UniqueProcessId; // HANDLE .UniqueProcessId\n                if (base != name) {\n                    char path[rt_files_max_path];\n                    match = rt_processes.nameof(pid, path, rt_countof(path)) == 0 &&\n                            rt_str.iends(path, name);\n//                  rt_println(\"\\\"%s\\\" -> \\\"%s\\\" match: %d\", name, path, match);\n                }\n            }\n            if (match) {\n                if (la != null && count < la->size && la->pids != null) {\n                    la->pids[count] = pid;\n                }\n                count++;\n                if (la != null && la->each != null && !la->each(la, pid)) {\n                    break;\n                }\n            }\n            proc = proc->NextEntryOffset != 0 ? (SYSTEM_PROCESS_INFORMATION*)\n                ((uint8_t*)proc + proc->NextEntryOffset) : null;\n        }\n    }\n    if (data != null) { rt_heap.deallocate(null, data); }\n    rt_assert(count <= (uint64_t)INT32_MAX);\n    return (int32_t)count;\n}\n\nstatic errno_t rt_processes_nameof(uint64_t pid, char* name, int32_t count) {\n    rt_assert(name != null && count > 0);\n    errno_t r = 0;\n    name[0] = 0;\n    HANDLE p = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);\n    if (p != null) {\n        r = rt_b2e(GetModuleFileNameExA(p, null, name, count));\n        name[count - 1] = 0; // ensure zero termination\n        rt_win32_close_handle(p);\n    } else {\n        r = ERROR_NOT_FOUND;\n    }\n    return r;\n}\n\nstatic bool rt_processes_present(uint64_t pid) {\n    void* h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, (DWORD)pid);\n    bool b = h != null;\n    if (h != null) { rt_win32_close_handle(h); }\n    return b;\n}\n\nstatic bool rt_processes_first_pid(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    lambda->pids[0] = pid;\n    return false;\n}\n\nstatic uint64_t rt_processes_pid(const char* pname) {\n    uint64_t first[1] = {0};\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_first_pid,\n        .pids = first,\n        .size  = 1,\n        .count = 0,\n        .timeout = 0,\n        .error = 0\n    };\n    rt_processes_for_each_pidof(pname, &lambda);\n    return first[0];\n}\n\nstatic bool rt_processes_store_pid(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    if (lambda->pids != null && lambda->count < lambda->size) {\n        lambda->pids[lambda->count++] = pid;\n    }\n    return true; // always - need to count all\n}\n\nstatic errno_t rt_processes_pids(const char* pname, uint64_t* pids/*[size]*/,\n        int32_t size, int32_t *count) {\n    *count = 0;\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_store_pid,\n        .pids = pids,\n        .size = (size_t)size,\n        .count = 0,\n        .timeout = 0,\n        .error = 0\n    };\n    *count = rt_processes_for_each_pidof(pname, &lambda);\n    return (int32_t)lambda.count == *count ? 0 : ERROR_MORE_DATA;\n}\n\nstatic errno_t rt_processes_kill(uint64_t pid, fp64_t timeout) {\n    DWORD milliseconds = timeout < 0 ? INFINITE : (DWORD)(timeout * 1000);\n    enum { access = PROCESS_QUERY_LIMITED_INFORMATION |\n                    PROCESS_TERMINATE | SYNCHRONIZE };\n    rt_assert((DWORD)pid == pid); // Windows... HANDLE vs DWORD in different APIs\n    errno_t r = ERROR_NOT_FOUND;\n    HANDLE h = OpenProcess(access, 0, (DWORD)pid);\n    if (h != null) {\n        char path[rt_files_max_path];\n        path[0] = 0;\n        r = rt_b2e(TerminateProcess(h, ERROR_PROCESS_ABORTED));\n        if (r == 0) {\n            DWORD ix = WaitForSingleObject(h, milliseconds);\n            r = rt_wait_ix2e(ix);\n        } else {\n            DWORD bytes = rt_countof(path);\n            errno_t rq = rt_b2e(QueryFullProcessImageNameA(h, 0, path, &bytes));\n            if (rq != 0) {\n                rt_println(\"QueryFullProcessImageNameA(pid=%d, h=%p) \"\n                        \"failed %s\", pid, h, rt_strerr(rq));\n            }\n        }\n        rt_win32_close_handle(h);\n        if (r == ERROR_ACCESS_DENIED) { // special case\n            rt_thread.sleep_for(0.015); // need to wait a bit\n            HANDLE retry = OpenProcess(access, 0, (DWORD)pid);\n            // process may have died before we have chance to terminate it:\n            if (retry == null) {\n                rt_println(\"TerminateProcess(pid=%d, h=%p, im=%s) \"\n                        \"failed but zombie died after: %s\",\n                        pid, h, path, rt_strerr(r));\n                r = 0;\n            } else {\n                rt_win32_close_handle(retry);\n            }\n        }\n        if (r != 0) {\n            rt_println(\"TerminateProcess(pid=%d, h=%p, im=%s) failed %s\",\n                pid, h, path, rt_strerr(r));\n        }\n    }\n    if (r != 0) { errno = r; }\n    return r;\n}\n\nstatic bool rt_processes_kill_one(rt_processes_pidof_lambda_t* lambda, uint64_t pid) {\n    errno_t r = rt_processes_kill(pid, lambda->timeout);\n    if (r != 0) { lambda->error = r; }\n    return true; // keep going\n}\n\nstatic errno_t rt_processes_kill_all(const char* name, fp64_t timeout) {\n    rt_processes_pidof_lambda_t lambda = {\n        .each = rt_processes_kill_one,\n        .pids = null,\n        .size  = 0,\n        .count = 0,\n        .timeout = timeout,\n        .error = 0\n    };\n    int32_t c = rt_processes_for_each_pidof(name, &lambda);\n    return c == 0 ? ERROR_NOT_FOUND : lambda.error;\n}\n\nstatic bool rt_processes_is_elevated(void) { // Is process running as Admin / System ?\n    BOOL elevated = false;\n    PSID administrators_group = null;\n    // Allocate and initialize a SID of the administrators group.\n    SID_IDENTIFIER_AUTHORITY administrators_group_authority = SECURITY_NT_AUTHORITY;\n    errno_t r = rt_b2e(AllocateAndInitializeSid(&administrators_group_authority, 2,\n                SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,\n                0, 0, 0, 0, 0, 0, &administrators_group));\n    if (r != 0) {\n        rt_println(\"AllocateAndInitializeSid() failed %s\", rt_strerr(r));\n    }\n    PSID system_ops = null;\n    SID_IDENTIFIER_AUTHORITY system_ops_authority = SECURITY_NT_AUTHORITY;\n    r = rt_b2e(AllocateAndInitializeSid(&system_ops_authority, 2,\n            SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_SYSTEM_OPS,\n            0, 0, 0, 0, 0, 0, &system_ops));\n    if (r != 0) {\n        rt_println(\"AllocateAndInitializeSid() failed %s\", rt_strerr(r));\n    }\n    if (administrators_group != null) {\n        r = rt_b2e(CheckTokenMembership(null, administrators_group, &elevated));\n    }\n    if (system_ops != null && !elevated) {\n        r = rt_b2e(CheckTokenMembership(null, administrators_group, &elevated));\n    }\n    if (administrators_group != null) { FreeSid(administrators_group); }\n    if (system_ops != null) { FreeSid(system_ops); }\n    if (r != 0) {\n        rt_println(\"failed %s\", rt_strerr(r));\n    }\n    return elevated;\n}\n\nstatic errno_t rt_processes_restart_elevated(void) {\n    errno_t r = 0;\n    if (!rt_processes.is_elevated()) {\n        const char* path = rt_processes.name();\n        SHELLEXECUTEINFOA sei = { sizeof(sei) };\n        sei.lpVerb = \"runas\";\n        sei.lpFile = path;\n        sei.hwnd = null;\n        sei.nShow = SW_NORMAL;\n        r = rt_b2e(ShellExecuteExA(&sei));\n        if (r == ERROR_CANCELLED) {\n            rt_println(\"The user unable or refused to allow privileges elevation\");\n        } else if (r == 0) {\n            rt_core.exit(0); // second copy of the app is running now\n        }\n    }\n    return r;\n}\n\nstatic void rt_processes_close_pipes(STARTUPINFOA* si,\n        HANDLE *read_out,\n        HANDLE *read_err,\n        HANDLE *write_in) {\n    if (si->hStdOutput != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdOutput); }\n    if (si->hStdError  != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdError);  }\n    if (si->hStdInput  != INVALID_HANDLE_VALUE) { rt_win32_close_handle(si->hStdInput);  }\n    if (*read_out != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*read_out); }\n    if (*read_err != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*read_err); }\n    if (*write_in != INVALID_HANDLE_VALUE) { rt_win32_close_handle(*write_in); }\n}\n\nstatic errno_t rt_processes_child_read(rt_stream_if* out, HANDLE pipe) {\n    char data[32 * 1024]; // Temporary buffer for reading\n    DWORD available = 0;\n    errno_t r = rt_b2e(PeekNamedPipe(pipe, null, sizeof(data), null,\n                                 &available, null));\n    if (r != 0) {\n        if (r != ERROR_BROKEN_PIPE) { // unexpected!\n//          rt_println(\"PeekNamedPipe() failed %s\", rt_strerr(r));\n        }\n        // process has exited and closed the pipe\n        rt_assert(r == ERROR_BROKEN_PIPE);\n    } else if (available > 0) {\n        DWORD bytes_read = 0;\n        r = rt_b2e(ReadFile(pipe, data, sizeof(data), &bytes_read, null));\n//      rt_println(\"r: %d bytes_read: %d\", r, bytes_read);\n        if (out != null) {\n            if (r == 0) {\n                r = out->write(out, data, bytes_read, null);\n            }\n        } else {\n            // no one interested - drop on the floor\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_child_write(rt_stream_if* in, HANDLE pipe) {\n    errno_t r = 0;\n    if (in != null) {\n        uint8_t  memory[32 * 1024]; // Temporary buffer for reading\n        uint8_t* data = memory;\n        int64_t bytes_read = 0;\n        in->read(in, data, sizeof(data), &bytes_read);\n        while (r == 0 && bytes_read > 0) {\n            DWORD bytes_written = 0;\n            r = rt_b2e(WriteFile(pipe, data, (DWORD)bytes_read,\n                             &bytes_written, null));\n            rt_println(\"r: %d bytes_written: %d\", r, bytes_written);\n            rt_assert((int32_t)bytes_written <= bytes_read);\n            data += bytes_written;\n            bytes_read -= bytes_written;\n        }\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_run(rt_processes_child_t* child) {\n    const fp64_t deadline = rt_clock.seconds() + child->timeout;\n    errno_t r = 0;\n    STARTUPINFOA si = {\n        .cb = sizeof(STARTUPINFOA),\n        .hStdInput  = INVALID_HANDLE_VALUE,\n        .hStdOutput = INVALID_HANDLE_VALUE,\n        .hStdError  = INVALID_HANDLE_VALUE,\n        .dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,\n        .wShowWindow = SW_HIDE\n    };\n    SECURITY_ATTRIBUTES sa = { sizeof(sa), null, true };  // Inheritable handles\n    PROCESS_INFORMATION pi = {0};\n    HANDLE read_out = INVALID_HANDLE_VALUE;\n    HANDLE read_err = INVALID_HANDLE_VALUE;\n    HANDLE write_in = INVALID_HANDLE_VALUE;\n    errno_t ro = rt_b2e(CreatePipe(&read_out, &si.hStdOutput, &sa, 0));\n    errno_t re = rt_b2e(CreatePipe(&read_err, &si.hStdError,  &sa, 0));\n    errno_t ri = rt_b2e(CreatePipe(&si.hStdInput, &write_in,  &sa, 0));\n    if (ro != 0 || re != 0 || ri != 0) {\n        rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        if (ro != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(ro)); r = ro; }\n        if (re != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(re)); r = re; }\n        if (ri != 0) { rt_println(\"CreatePipe() failed %s\", rt_strerr(ri)); r = ri; }\n    }\n    if (r == 0) {\n        r = rt_b2e(CreateProcessA(null, rt_str.drop_const(child->command),\n                null, null, true, CREATE_NO_WINDOW, null, null, &si, &pi));\n        if (r != 0) {\n            rt_println(\"CreateProcess() failed %s\", rt_strerr(r));\n            rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        }\n    }\n    if (r == 0) {\n        // not relevant: stdout can be written in other threads\n        rt_win32_close_handle(pi.hThread);\n        pi.hThread = null;\n        // need to close si.hStdO* handles on caller side so,\n        // when the process closes handles of the pipes, EOF happens\n        // on caller side with io result ERROR_BROKEN_PIPE\n        // indicating no more data can be read or written\n        rt_win32_close_handle(si.hStdOutput);\n        rt_win32_close_handle(si.hStdError);\n        rt_win32_close_handle(si.hStdInput);\n        si.hStdOutput = INVALID_HANDLE_VALUE;\n        si.hStdError  = INVALID_HANDLE_VALUE;\n        si.hStdInput  = INVALID_HANDLE_VALUE;\n        bool done = false;\n        while (!done && r == 0) {\n            if (child->timeout > 0 && rt_clock.seconds() > deadline) {\n                r = rt_b2e(TerminateProcess(pi.hProcess, ERROR_SEM_TIMEOUT));\n                if (r != 0) {\n                    rt_println(\"TerminateProcess() failed %s\", rt_strerr(r));\n                } else {\n                    done = true;\n                }\n            }\n            if (r == 0) { r = rt_processes_child_write(child->in, write_in); }\n            if (r == 0) { r = rt_processes_child_read(child->out, read_out); }\n            if (r == 0) { r = rt_processes_child_read(child->err, read_err); }\n            if (!done) {\n                DWORD ix = WaitForSingleObject(pi.hProcess, 0);\n                // ix == 0 means process has exited (or terminated)\n                // r == ERROR_BROKEN_PIPE process closed one of the handles\n                done = ix == WAIT_OBJECT_0 || r == ERROR_BROKEN_PIPE;\n            }\n            // to avoid tight loop 100% cpu utilization:\n            if (!done) { rt_thread.yield(); }\n        }\n        // broken pipe actually signifies EOF on the pipe\n        if (r == ERROR_BROKEN_PIPE) { r = 0; } // not an error\n//      if (r != 0) { rt_println(\"pipe loop failed %s\", rt_strerr(r));}\n        DWORD xc = 0;\n        errno_t rx = rt_b2e(GetExitCodeProcess(pi.hProcess, &xc));\n        if (rx == 0) {\n            child->exit_code = xc;\n        } else {\n            rt_println(\"GetExitCodeProcess() failed %s\", rt_strerr(rx));\n            if (r != 0) { r = rx; } // report earliest error\n        }\n        rt_processes_close_pipes(&si, &read_out, &read_err, &write_in);\n        // expected never to fail\n        rt_win32_close_handle(pi.hProcess);\n    }\n    return r;\n}\n\ntypedef struct {\n    rt_stream_if stream;\n    rt_stream_if* output;\n    errno_t error;\n} rt_processes_io_merge_out_and_err_if;\n\nstatic errno_t rt_processes_merge_write(rt_stream_if* stream, const void* data,\n        int64_t bytes, int64_t* transferred) {\n    if (transferred != null) { *transferred = 0; }\n    rt_processes_io_merge_out_and_err_if* s =\n        (rt_processes_io_merge_out_and_err_if*)stream;\n    if (s->output != null && bytes > 0) {\n        s->error = s->output->write(s->output, data, bytes, transferred);\n    }\n    return s->error;\n}\n\nstatic errno_t rt_processes_open(const char* command, int32_t *exit_code,\n        rt_stream_if* output,  fp64_t timeout) {\n    rt_not_null(output);\n    rt_processes_io_merge_out_and_err_if merge_out_and_err = {\n        .stream ={ .write = rt_processes_merge_write },\n        .output = output,\n        .error = 0\n    };\n    rt_processes_child_t child = {\n        .command = command,\n        .in = null,\n        .out = &merge_out_and_err.stream,\n        .err = &merge_out_and_err.stream,\n        .exit_code = 0,\n        .timeout = timeout\n    };\n    errno_t r = rt_processes.run(&child);\n    if (exit_code != null) { *exit_code = (int32_t)child.exit_code; }\n    uint8_t zero = 0; // zero termination\n    merge_out_and_err.stream.write(&merge_out_and_err.stream, &zero, 1, null);\n    if (r == 0 && merge_out_and_err.error != 0) {\n        r = merge_out_and_err.error; // zero termination is not guaranteed\n    }\n    return r;\n}\n\nstatic errno_t rt_processes_spawn(const char* command) {\n    errno_t r = 0;\n    STARTUPINFOA si = {\n        .cb = sizeof(STARTUPINFOA),\n        .dwFlags     = STARTF_USESHOWWINDOW\n                     | CREATE_NEW_PROCESS_GROUP\n                     | DETACHED_PROCESS,\n        .wShowWindow = SW_HIDE,\n        .hStdInput  = INVALID_HANDLE_VALUE,\n        .hStdOutput = INVALID_HANDLE_VALUE,\n        .hStdError  = INVALID_HANDLE_VALUE\n    };\n    const DWORD flags = CREATE_BREAKAWAY_FROM_JOB\n                | CREATE_NO_WINDOW\n                | CREATE_NEW_PROCESS_GROUP\n                | DETACHED_PROCESS;\n    PROCESS_INFORMATION pi = { .hProcess = null, .hThread = null };\n    r = rt_b2e(CreateProcessA(null, rt_str.drop_const(command), null, null,\n            /*bInheritHandles:*/false, flags, null, null, &si, &pi));\n    if (r == 0) { // Close handles immediately\n        rt_win32_close_handle(pi.hProcess);\n        rt_win32_close_handle(pi.hThread);\n    } else {\n        rt_println(\"CreateProcess() failed %s\", rt_strerr(r));\n    }\n    return r;\n}\n\nstatic const char* rt_processes_name(void) {\n    static char mn[rt_files_max_path];\n    if (mn[0] == 0) {\n        rt_fatal_win32err(GetModuleFileNameA(null, mn, rt_countof(mn)));\n    }\n    return mn;\n}\n\n#ifdef RT_TESTS\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                       \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                                   \\\n    }                                                           \\\n} while (0)\n\nstatic void rt_processes_test(void) {\n    #ifdef RT_TESTS // in alphabetical order\n    const char* names[] = { \"svchost\", \"RuntimeBroker\", \"conhost\" };\n    for (int32_t j = 0; j < rt_countof(names); j++) {\n        int32_t size  = 0;\n        int32_t count = 0;\n        uint64_t* pids = null;\n        errno_t r = rt_processes.pids(names[j], null, size, &count);\n        while (r == ERROR_MORE_DATA && count > 0) {\n            size = count * 2; // set of processes may change rapidly\n            r = rt_heap.reallocate(null, (void**)&pids,\n                                  (int64_t)sizeof(uint64_t) * (int64_t)size,\n                                  false);\n            if (r == 0) {\n                r = rt_processes.pids(names[j], pids, size, &count);\n            }\n        }\n        if (r == 0 && count > 0) {\n            for (int32_t i = 0; i < count; i++) {\n                char path[256] = {0};\n                #pragma warning(suppress: 6011) // dereferencing null\n                r = rt_processes.nameof(pids[i], path, rt_countof(path));\n                if (r != ERROR_NOT_FOUND) {\n                    rt_assert(r == 0 && path[0] != 0);\n                    verbose(\"%6d %s %s\", pids[i], path, rt_strerr(r));\n                }\n            }\n        }\n        rt_heap.deallocate(null, pids);\n    }\n    // test popen()\n    int32_t xc = 0;\n    char data[32 * 1024];\n    rt_stream_memory_if output;\n    rt_streams.write_only(&output, data, rt_countof(data));\n    const char* cmd = \"cmd /c dir 2>nul >nul\";\n    errno_t r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c dir \\\"folder that does not exist\\\\\\\"\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c dir\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 99999.0);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    rt_streams.write_only(&output, data, rt_countof(data));\n    cmd = \"cmd /c timeout 1\";\n    r = rt_processes.popen(cmd, &xc, &output.stream, 1.0E-9);\n    verbose(\"r: %d xc: %d output:\\n%s\", r, xc, data);\n    #endif\n}\n\n#pragma pop_macro(\"verbose\")\n\n#else\n\nstatic void rt_processes_test(void) { }\n\n#endif\n\nrt_processes_if rt_processes = {\n    .pid                 = rt_processes_pid,\n    .pids                = rt_processes_pids,\n    .nameof              = rt_processes_nameof,\n    .present             = rt_processes_present,\n    .kill                = rt_processes_kill,\n    .kill_all            = rt_processes_kill_all,\n    .is_elevated         = rt_processes_is_elevated,\n    .restart_elevated    = rt_processes_restart_elevated,\n    .run                 = rt_processes_run,\n    .popen               = rt_processes_open,\n    .spawn               = rt_processes_spawn,\n    .name                = rt_processes_name,\n    .test                = rt_processes_test\n};\n"
  },
  {
    "path": "src/rt/rt_static.c",
    "content": "#include \"rt/rt.h\"\n\nstatic void*   rt_static_symbol_reference[1024];\nstatic int32_t rt_static_symbol_reference_count;\n\nvoid* rt_force_symbol_reference(void* symbol) {\n    rt_assert(rt_static_symbol_reference_count <= rt_countof(rt_static_symbol_reference),\n        \"increase size of rt_static_symbol_reference[%d] to at least %d\",\n        rt_countof(rt_static_symbol_reference), rt_static_symbol_reference);\n    if (rt_static_symbol_reference_count < rt_countof(rt_static_symbol_reference)) {\n        rt_static_symbol_reference[rt_static_symbol_reference_count] = symbol;\n//      rt_println(\"rt_static_symbol_reference[%d] = %p\", rt_static_symbol_reference_count,\n//               rt_static_symbol_reference[symbol_reference_count]);\n        rt_static_symbol_reference_count++;\n    }\n    return symbol;\n}\n\n// test rt_static_init() { code } that will be executed in random\n// order but before main()\n\n#ifdef RT_TESTS\n\nstatic int32_t rt_static_init_function_called;\n\nstatic void rt_force_inline rt_static_init_function(void) {\n    rt_static_init_function_called = 1;\n}\n\nrt_static_init(static_init_test) { rt_static_init_function(); }\n\nvoid rt_static_init_test(void) {\n    rt_fatal_if(rt_static_init_function_called != 1,\n        \"static_init_function() expected to be called before main()\");\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nvoid rt_static_init_test(void) {}\n\n#endif\n"
  },
  {
    "path": "src/rt/rt_str.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic char* rt_str_drop_const(const char* s) {\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wcast-qual\"\n    #endif\n    return (char*)s;\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic pop\n    #endif\n}\n\nstatic int32_t rt_str_len(const char* s) { return (int32_t)strlen(s); }\n\nstatic int32_t rt_str_utf16len(const uint16_t* utf16) {\n    return (int32_t)wcslen(utf16);\n}\n\nstatic int32_t rt_str_utf8bytes(const char* s, int32_t b) {\n    rt_assert(b >= 1, \"should not be called with bytes < 1\");\n    const uint8_t* const u = (const uint8_t*)s;\n    // based on:\n    // https://stackoverflow.com/questions/66715611/check-for-valid-utf-8-encoding-in-c\n    if (b >= 1 && (u[0] & 0x80u) == 0x00u) {\n        return 1;\n    } else if (b > 1) {\n        uint32_t c = (u[0] << 8) | u[1];\n        // TODO: 0xC080 is a hack - consider removing\n        if (c == 0xC080) { return 2; } // 0xC080 as not zero terminating '\\0'\n        if (0xC280 <= c && c <= 0xDFBF && (c & 0xE0C0) == 0xC080) { return 2; }\n        if (b > 2) {\n            c = (c << 8) | u[2];\n            // reject utf16 surrogates:\n            if (0xEDA080 <= c && c <= 0xEDBFBF) { return 0; }\n            if (0xE0A080 <= c && c <= 0xEFBFBF && (c & 0xF0C0C0) == 0xE08080) {\n                return 3;\n            }\n            if (b > 3) {\n                c = (c << 8) | u[3];\n                if (0xF0908080 <= c && c <= 0xF48FBFBF &&\n                    (c & 0xF8C0C0C0) == 0xF0808080) {\n                    return 4;\n                }\n            }\n        }\n    }\n    return 0; // invalid utf8 sequence\n}\n\nstatic int32_t rt_str_glyphs(const char* utf8, int32_t bytes) {\n    rt_swear(bytes >= 0);\n    bool ok = true;\n    int32_t i = 0;\n    int32_t k = 1;\n    while (i < bytes && ok) {\n        const int32_t b = rt_str.utf8bytes(utf8 + i, bytes - i);\n        ok = 0 < b && i + b <= bytes;\n        if (ok) { i += b; k++; }\n    }\n    return ok ? k - 1 : -1;\n}\n\nstatic void rt_str_lower(char* d, int32_t capacity, const char* s) {\n    int32_t n = rt_str.len(s);\n    rt_swear(capacity > n);\n    for (int32_t i = 0; i < n; i++) { d[i] = (char)tolower(s[i]); }\n    d[n] = 0;\n}\n\nstatic void rt_str_upper(char* d, int32_t capacity, const char* s) {\n    int32_t n = rt_str.len(s);\n    rt_swear(capacity > n);\n    for (int32_t i = 0; i < n; i++) { d[i] = (char)toupper(s[i]); }\n    d[n] = 0;\n}\n\nstatic bool rt_str_starts(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && memcmp(s1, s2, n2) == 0;\n}\n\nstatic bool rt_str_ends(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && memcmp(s1 + n1 - n2, s2, n2) == 0;\n}\n\nstatic bool rt_str_i_starts(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && strnicmp(s1, s2, n2) == 0;\n\n}\n\nstatic bool rt_str_i_ends(const char* s1, const char* s2) {\n    int32_t n1 = (int32_t)strlen(s1);\n    int32_t n2 = (int32_t)strlen(s2);\n    return n1 >= n2 && strnicmp(s1 + n1 - n2, s2, n2) == 0;\n}\n\nstatic int32_t rt_str_utf8_bytes(const uint16_t* utf16, int32_t chars) {\n    // If `chars` argument is -1, the function utf8_bytes includes the zero\n    // terminating character in the conversion and the returned byte count.\n    // Function will fail (return 0) on incomplete surrogate pairs like\n    // 0xD83D without following 0xDC1E https://compart.com/en/unicode/U+1F41E\n    if (chars == 0) { return 0; }\n    if (chars < 0 && utf16[0] == 0x0000) { return 1; }\n    const int32_t required_bytes_count =\n        WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,\n        utf16, chars, null, 0, null, null);\n    if (required_bytes_count == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"WideCharToMultiByte() failed %s\", rt_strerr(r));\n        rt_core.set_err(r);\n    }\n    return required_bytes_count == 0 ? -1 : required_bytes_count;\n}\n\nstatic int32_t rt_str_utf16_chars(const char* utf8, int32_t bytes) {\n    // If `bytes` argument is -1, the function utf16_chars() includes the zero\n    // terminating character in the conversion and the returned character count.\n    if (bytes == 0) { return 0; }\n    if (bytes < 0 && utf8[0] == 0x00) { return 1; }\n    const int32_t required_wide_chars_count =\n        MultiByteToWideChar(CP_UTF8, 0, utf8, bytes, null, 0);\n    if (required_wide_chars_count == 0) {\n        errno_t r = rt_core.err();\n        rt_println(\"MultiByteToWideChar() failed %s\", rt_strerr(r));\n        rt_core.set_err(r);\n    }\n    return required_wide_chars_count == 0 ? -1 : required_wide_chars_count;\n}\n\nstatic errno_t rt_str_utf16to8(char* utf8, int32_t capacity,\n        const uint16_t* utf16, int32_t chars) {\n    if (chars == 0) { return 0; }\n    if (chars < 0 && utf16[0] == 0x0000) {\n        rt_swear(capacity >= 1);\n        utf8[0] = 0x00;\n        return 0;\n    }\n    const int32_t required = rt_str.utf8_bytes(utf16, chars);\n    errno_t r = required < 0 ? rt_core.err() : 0;\n    if (r == 0) {\n        rt_swear(required > 0 && capacity >= required);\n        int32_t bytes = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,\n                            utf16, chars, utf8, capacity, null, null);\n        rt_swear(required == bytes);\n    }\n    return r;\n}\n\nstatic errno_t rt_str_utf8to16(uint16_t* utf16, int32_t capacity,\n        const char* utf8, int32_t bytes) {\n    const int32_t required = rt_str.utf16_chars(utf8, bytes);\n    errno_t r = required < 0 ? rt_core.err() : 0;\n    if (r == 0) {\n        rt_swear(required >= 0 && capacity >= required);\n        int32_t count = MultiByteToWideChar(CP_UTF8, 0, utf8, bytes,\n                                            utf16, capacity);\n        rt_swear(required == count);\n#if 0 // TODO: incorrect need output != input\n        if (count > 0 && !IsNormalizedString(NormalizationC, utf16, count)) {\n            rt_core.set_err(0);\n            int32_t n = NormalizeString(NormalizationC, utf16, count, utf16, count);\n            if (n <= 0) {\n                r = rt_core.err();\n                rt_println(\"NormalizeString() failed %s\", rt_strerr(r));\n            }\n        }\n#endif \n    }\n    return r;\n}\n\nstatic bool rt_str_utf16_is_low_surrogate(uint16_t utf16char) {\n    return 0xDC00 <= utf16char && utf16char <= 0xDFFF;\n}\n\nstatic bool rt_str_utf16_is_high_surrogate(uint16_t utf16char) {\n    return 0xD800 <= utf16char && utf16char <= 0xDBFF;\n}\n\nstatic uint32_t rt_str_utf32(const char* utf8, int32_t bytes) {\n    uint32_t utf32 = 0;\n    if ((utf8[0] & 0x80) == 0) {\n        utf32 = utf8[0];\n        rt_swear(bytes == 1);\n    } else if ((utf8[0] & 0xE0) == 0xC0) {\n        utf32  = (utf8[0] & 0x1F) << 6;\n        utf32 |= (utf8[1] & 0x3F);\n        rt_swear(bytes == 2);\n    } else if ((utf8[0] & 0xF0) == 0xE0) {\n        utf32  = (utf8[0] & 0x0F) << 12;\n        utf32 |= (utf8[1] & 0x3F) <<  6;\n        utf32 |= (utf8[2] & 0x3F);\n        rt_swear(bytes == 3);\n    } else if ((utf8[0] & 0xF8) == 0xF0) {\n        utf32  = (utf8[0] & 0x07) << 18;\n        utf32 |= (utf8[1] & 0x3F) << 12;\n        utf32 |= (utf8[2] & 0x3F) <<  6;\n        utf32 |= (utf8[3] & 0x3F);\n        rt_swear(bytes == 4);\n    } else {\n        rt_swear(false);\n    }\n    return utf32;\n}\n\nstatic void rt_str_format_va(char* utf8, int32_t count, const char* format,\n        va_list va) {\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wformat-nonliteral\"\n    #endif\n    vsnprintf(utf8, (size_t)count, format, va);\n    utf8[count - 1] = 0;\n    #if defined(__GNUC__) || defined(__clang__)\n    #pragma GCC diagnostic pop\n    #endif\n}\n\nstatic void rt_str_format(char* utf8, int32_t count, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_str.format_va(utf8, count, format, va);\n    va_end(va);\n}\n\nstatic rt_str1024_t rt_str_error_for_language(int32_t error, LANGID language) {\n    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;\n    HMODULE module = null;\n    HRESULT hr = 0 <= error && error <= 0xFFFF ?\n        HRESULT_FROM_WIN32((uint32_t)error) : (HRESULT)error;\n    if ((error & 0xC0000000U) == 0xC0000000U) {\n        // https://stackoverflow.com/questions/25566234/how-to-convert-specific-ntstatus-value-to-the-hresult\n        static HMODULE ntdll; // RtlNtStatusToDosError implies linking to ntdll\n        if (ntdll == null) { ntdll = GetModuleHandleA(\"ntdll.dll\"); }\n        if (ntdll == null) { ntdll = LoadLibraryA(\"ntdll.dll\"); }\n        module = ntdll;\n        hr = HRESULT_FROM_WIN32(RtlNtStatusToDosError((NTSTATUS)error));\n        flags |= FORMAT_MESSAGE_FROM_HMODULE;\n    }\n    rt_str1024_t text;\n    uint16_t utf16[rt_countof(text.s)];\n    DWORD count = FormatMessageW(flags, module, hr, language,\n            utf16, rt_countof(utf16) - 1, (va_list*)null);\n    utf16[rt_countof(utf16) - 1] = 0; // always\n    // If FormatMessageW() succeeds, the return value is the number of utf16\n    // characters stored in the output buffer, excluding the terminating zero.\n    if (count > 0) {\n        rt_swear(count < rt_countof(utf16));\n        utf16[count] = 0;\n        // remove trailing '\\r\\n'\n        int32_t k = count;\n        if (k > 0 && utf16[k - 1] == '\\n') { utf16[k - 1] = 0; }\n        k = (int32_t)rt_str.len16(utf16);\n        if (k > 0 && utf16[k - 1] == '\\r') { utf16[k - 1] = 0; }\n        char message[rt_countof(text.s)];\n        const int32_t bytes = rt_str.utf8_bytes(utf16, -1);\n        if (bytes >= rt_countof(message)) {\n            rt_str_printf(message, \"error message is too long: %d bytes\", bytes);\n        } else {\n            rt_str.utf16to8(message, rt_countof(message), utf16, -1);\n        }\n        // truncating printf to string:\n        rt_str_printf(text.s, \"0x%08X(%d) \\\"%s\\\"\", error, error, message);\n    } else {\n        rt_str_printf(text.s, \"0x%08X(%d)\", error, error);\n    }\n    return text;\n}\n\nstatic rt_str1024_t rt_str_error(int32_t error) {\n    const LANGID language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);\n    return rt_str_error_for_language(error, language);\n}\n\nstatic rt_str1024_t rt_str_error_nls(int32_t error) {\n    const LANGID language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);\n    return rt_str_error_for_language(error, language);\n}\n\nstatic const char* rt_str_grouping_separator(void) {\n    #ifdef WINDOWS\n        // en-US Windows 10/11:\n        // grouping_separator == \",\"\n        // decimal_separator  == \".\"\n        static char grouping_separator[8];\n        if (grouping_separator[0] == 0x00) {\n            errno_t r = rt_b2e(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,\n                grouping_separator, sizeof(grouping_separator)));\n            rt_swear(r == 0 && grouping_separator[0] != 0);\n        }\n        return grouping_separator;\n    #else\n        // en-US Windows 10/11:\n        // grouping_separator == \"\"\n        // decimal_separator  == \".\"\n        struct lconv *locale_info = localeconv();\n        const char* grouping_separator = null;\n        if (grouping_separator == null) {\n            grouping_separator = locale_info->thousands_sep;\n            swear(grouping_separator != null);\n        }\n        return grouping_separator;\n    #endif\n}\n\n// Posix and Win32 C runtime:\n//   #include <locale.h>\n//   struct lconv *locale_info = localeconv();\n//   const char* grouping_separator = locale_info->thousands_sep;\n//   const char* decimal_separator = locale_info->decimal_point;\n// en-US Windows 1x:\n// grouping_separator == \"\"\n// decimal_separator  == \".\"\n//\n// Win32 API:\n//   rt_b2e(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,\n//       grouping_separator, sizeof(grouping_separator)));\n//   rt_b2e(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,\n//       decimal_separator, sizeof(decimal_separator)));\n// en-US Windows 1x:\n// grouping_separator == \",\"\n// decimal_separator  == \".\"\n\nstatic rt_str64_t rt_str_int64_dg(int64_t v, // digit_grouped\n        bool uint, const char* gs) { // grouping separator: gs\n    // sprintf format %`lld may not be implemented or\n    // does not respect locale or UI separators...\n    // Do it hard way:\n    const int32_t m = (int32_t)strlen(gs);\n    rt_swear(m < 5); // utf-8 4 bytes max\n    // 64 calls per thread 32 or less bytes each because:\n    // \"18446744073709551615\" 21 characters + 6x4 groups:\n    // \"18'446'744'073'709'551'615\" 27 characters\n    rt_str64_t text;\n    enum { max_text_bytes = rt_countof(text.s) };\n    int64_t abs64 = v < 0 ? -v : v; // incorrect for INT64_MIN\n    uint64_t n = uint ? (uint64_t)v :\n        (v != INT64_MIN ? (uint64_t)abs64 : (uint64_t)INT64_MIN);\n    int32_t i = 0;\n    int32_t groups[8]; // 2^63 - 1 ~= 9 x 10^19 upto 7 groups of 3 digits\n    do {\n        groups[i] = n % 1000;\n        n = n / 1000;\n        i++;\n    } while (n > 0);\n    const int32_t gc = i - 1; // group count\n    char* s = text.s;\n    if (v < 0 && !uint) { *s++ = '-'; } // sign\n    int32_t r = max_text_bytes - 1;\n    while (i > 0) {\n        i--;\n        rt_assert(r > 3 + m);\n        if (i == gc) {\n            rt_str.format(s, r, \"%d%s\", groups[i], gc > 0 ? gs : \"\");\n        } else {\n            rt_str.format(s, r, \"%03d%s\", groups[i], i > 0 ? gs : \"\");\n        }\n        int32_t k = (int32_t)strlen(s);\n        r -= k;\n        s += k;\n    }\n    *s = 0;\n    return text;\n}\n\nstatic rt_str64_t rt_str_int64(int64_t v) {\n    return rt_str_int64_dg(v, false, rt_glyph_hair_space);\n}\n\nstatic rt_str64_t rt_str_uint64(uint64_t v) {\n    return rt_str_int64_dg(v, true, rt_glyph_hair_space);\n}\n\nstatic rt_str64_t rt_str_int64_lc(int64_t v) {\n    return rt_str_int64_dg(v, false, rt_str_grouping_separator());\n}\n\nstatic rt_str64_t rt_str_uint64_lc(uint64_t v) {\n    return rt_str_int64_dg(v, true, rt_str_grouping_separator());\n}\n\nstatic rt_str128_t rt_str_fp(const char* format, fp64_t v) {\n    static char decimal_separator[8];\n    if (decimal_separator[0] == 0) {\n        errno_t r = rt_b2e(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,\n            decimal_separator, sizeof(decimal_separator)));\n        rt_swear(r == 0 && decimal_separator[0] != 0);\n    }\n    rt_swear(strlen(decimal_separator) <= 4);\n    rt_str128_t f; // formatted float point\n    // snprintf format does not handle thousands separators on all know runtimes\n    // and respects setlocale() on Un*x systems but in MS runtime only when\n    // _snprintf_l() is used.\n    f.s[0] = 0x00;\n    rt_str.format(f.s, rt_countof(f.s), format, v);\n    f.s[rt_countof(f.s) - 1] = 0x00;\n    rt_str128_t text;\n    char* s = f.s;\n    char* d = text.s;\n    while (*s != 0x00) {\n        if (*s == '.') {\n            const char* sep = decimal_separator;\n            while (*sep != 0x00) { *d++ = *sep++; }\n            s++;\n        } else {\n            *d++ = *s++;\n        }\n    }\n    *d = 0x00;\n    // TODO: It's possible to handle mantissa grouping but...\n    // Not clear if human expects it in 5 digits or 3 digits chunks\n    // and unfortunately locale does not specify how\n    return text;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_str_test(void) {\n    rt_swear(rt_str.len(\"hello\") == 5);\n    rt_swear(rt_str.starts(\"hello world\", \"hello\"));\n    rt_swear(rt_str.ends(\"hello world\", \"world\"));\n    rt_swear(rt_str.istarts(\"hello world\", \"HeLlO\"));\n    rt_swear(rt_str.iends(\"hello world\", \"WoRlD\"));\n    char ls[20] = {0};\n    rt_str.lower(ls, rt_countof(ls), \"HeLlO WoRlD\");\n    rt_swear(strcmp(ls, \"hello world\") == 0);\n    char upper[11] = {0};\n    rt_str.upper(upper, rt_countof(upper), \"hello12345\");\n    rt_swear(strcmp(upper,  \"HELLO12345\") == 0);\n    #pragma push_macro(\"glyph_chinese_one\")\n    #pragma push_macro(\"glyph_chinese_two\")\n    #pragma push_macro(\"glyph_teddy_bear\")\n    #pragma push_macro(\"glyph_ice_cube\")\n    #define glyph_chinese_one \"\\xE5\\xA3\\xB9\"\n    #define glyph_chinese_two \"\\xE8\\xB4\\xB0\"\n    #define glyph_teddy_bear  \"\\xF0\\x9F\\xA7\\xB8\"\n    #define glyph_ice_cube    \"\\xF0\\x9F\\xA7\\x8A\"\n    const char* utf8_str =\n            glyph_teddy_bear\n            \"0\"\n            rt_glyph_chinese_jin4 rt_glyph_chinese_gong\n            \"3456789 \"\n            glyph_ice_cube;\n    rt_swear(rt_str.utf8bytes(\"\\x01\", 1) == 1);\n    rt_swear(rt_str.utf8bytes(\"\\x7F\", 1) == 1);\n    rt_swear(rt_str.utf8bytes(\"\\x80\", 1) == 0);\n//  swear(rt_str.utf8bytes(glyph_chinese_one, 0) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 1) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 2) == 0);\n    rt_swear(rt_str.utf8bytes(glyph_chinese_one, 3) == 3);\n    rt_swear(rt_str.utf8bytes(glyph_teddy_bear,  4) == 4);\n    #pragma pop_macro(\"glyph_ice_cube\")\n    #pragma pop_macro(\"glyph_teddy_bear\")\n    #pragma pop_macro(\"glyph_chinese_two\")\n    #pragma pop_macro(\"glyph_chinese_one\")\n    uint16_t wide_str[100] = {0};\n    rt_str.utf8to16(wide_str, rt_countof(wide_str), utf8_str, -1);\n    char utf8[100] = {0};\n    rt_str.utf16to8(utf8, rt_countof(utf8), wide_str, -1);\n    uint16_t utf16[100];\n    rt_str.utf8to16(utf16, rt_countof(utf16), utf8, -1);\n    char narrow_str[100] = {0};\n    rt_str.utf16to8(narrow_str, rt_countof(narrow_str), utf16, -1);\n    rt_swear(strcmp(narrow_str, utf8_str) == 0);\n    char formatted[100];\n    rt_str.format(formatted, rt_countof(formatted), \"n: %d, s: %s\", 42, \"test\");\n    rt_swear(strcmp(formatted, \"n: 42, s: test\") == 0);\n    // numeric values digit grouping format:\n    rt_swear(strcmp(\"0\", rt_str.int64_dg(0, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-1\", rt_str.int64_dg(-1, false, \",\").s) == 0);\n    rt_swear(strcmp(\"999\", rt_str.int64_dg(999, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-999\", rt_str.int64_dg(-999, false, \",\").s) == 0);\n    rt_swear(strcmp(\"1,001\", rt_str.int64_dg(1001, true, \",\").s) == 0);\n    rt_swear(strcmp(\"-1,001\", rt_str.int64_dg(-1001, false, \",\").s) == 0);\n    rt_swear(strcmp(\"18,446,744,073,709,551,615\",\n        rt_str.int64_dg(UINT64_MAX, true, \",\").s) == 0\n    );\n    rt_swear(strcmp(\"9,223,372,036,854,775,807\",\n        rt_str.int64_dg(INT64_MAX, false, \",\").s) == 0\n    );\n    rt_swear(strcmp(\"-9,223,372,036,854,775,808\",\n        rt_str.int64_dg(INT64_MIN, false, \",\").s) == 0\n    );\n    //  see:\n    // https://en.wikipedia.org/wiki/Single-precision_floating-point_format\n    uint32_t pi_fp32 = 0x40490FDBULL; // 3.14159274101257324\n    rt_swear(strcmp(\"3.141592741\",\n                rt_str.fp(\"%.9f\", *(fp32_t*)&pi_fp32).s) == 0,\n          \"%s\", rt_str.fp(\"%.9f\", *(fp32_t*)&pi_fp32).s\n    );\n    //  3.141592741\n    //  ********^ (*** true digits ^ first rounded digit)\n    //    123456 (%.6f)\n    //\n    //  https://en.wikipedia.org/wiki/Double-precision_floating-point_format\n    uint64_t pi_fp64 = 0x400921FB54442D18ULL;\n    rt_swear(strcmp(\"3.141592653589793116\",\n                rt_str.fp(\"%.18f\", *(fp64_t*)&pi_fp64).s) == 0,\n          \"%s\", rt_str.fp(\"%.18f\", *(fp64_t*)&pi_fp64).s\n    );\n    //  3.141592653589793116\n    //  *****************^ (*** true digits ^ first rounded digit)\n    //    123456789012345 (%.15f)\n    //  https://en.wikipedia.org/wiki/Double-precision_floating-point_format\n    //\n    //  actual \"pi\" first 64 digits:\n    //  3.1415926535897932384626433832795028841971693993751058209749445923\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_str_test(void) {}\n\n#endif\n\nrt_str_if rt_str = {\n    .drop_const              = rt_str_drop_const,\n    .len                     = rt_str_len,\n    .len16                   = rt_str_utf16len,\n    .utf8bytes               = rt_str_utf8bytes,\n    .glyphs                  = rt_str_glyphs,\n    .lower                   = rt_str_lower,\n    .upper                   = rt_str_upper,\n    .starts                  = rt_str_starts,\n    .ends                    = rt_str_ends,\n    .istarts                 = rt_str_i_starts,\n    .iends                   = rt_str_i_ends,\n    .utf8_bytes              = rt_str_utf8_bytes,\n    .utf16_chars             = rt_str_utf16_chars,\n    .utf16to8                = rt_str_utf16to8,\n    .utf8to16                = rt_str_utf8to16,\n    .utf16_is_low_surrogate  = rt_str_utf16_is_low_surrogate,\n    .utf16_is_high_surrogate = rt_str_utf16_is_high_surrogate,\n    .utf32                   = rt_str_utf32,\n    .format                  = rt_str_format,\n    .format_va               = rt_str_format_va,\n    .error                   = rt_str_error,\n    .error_nls               = rt_str_error_nls,\n    .grouping_separator      = rt_str_grouping_separator,\n    .int64_dg                = rt_str_int64_dg,\n    .int64                   = rt_str_int64,\n    .uint64                  = rt_str_uint64,\n    .int64_lc                = rt_str_int64,\n    .uint64_lc               = rt_str_uint64,\n    .fp                      = rt_str_fp,\n    .test                    = rt_str_test\n};\n"
  },
  {
    "path": "src/rt/rt_streams.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nstatic errno_t rt_streams_memory_read(rt_stream_if* stream, void* data, int64_t bytes,\n        int64_t *transferred) {\n    rt_swear(bytes > 0);\n    rt_stream_memory_if* s = (rt_stream_memory_if*)stream;\n    rt_swear(0 <= s->pos_read && s->pos_read <= s->bytes_read,\n          \"bytes: %lld stream .pos: %lld .bytes: %lld\",\n          bytes, s->pos_read, s->bytes_read);\n    int64_t transfer = rt_min(bytes, s->bytes_read - s->pos_read);\n    memcpy(data, (const uint8_t*)s->data_read + s->pos_read, (size_t)transfer);\n    s->pos_read += transfer;\n    if (transferred != null) { *transferred = transfer; }\n    return 0;\n}\n\nstatic errno_t rt_streams_memory_write(rt_stream_if* stream, const void* data, int64_t bytes,\n        int64_t *transferred) {\n    rt_swear(bytes > 0);\n    rt_stream_memory_if* s = (rt_stream_memory_if*)stream;\n    rt_swear(0 <= s->pos_write && s->pos_write <= s->bytes_write,\n          \"bytes: %lld stream .pos: %lld .bytes: %lld\",\n          bytes, s->pos_write, s->bytes_write);\n    bool overflow = s->bytes_write - s->pos_write <= 0;\n    int64_t transfer = rt_min(bytes, s->bytes_write - s->pos_write);\n    memcpy((uint8_t*)s->data_write + s->pos_write, data, (size_t)transfer);\n    s->pos_write += transfer;\n    if (transferred != null) { *transferred = transfer; }\n    return overflow ? ERROR_INSUFFICIENT_BUFFER : 0;\n}\n\nstatic void rt_streams_read_only(rt_stream_memory_if* s,\n        const void* data, int64_t bytes) {\n    s->stream.read = rt_streams_memory_read;\n    s->stream.write = null;\n    s->data_read = data;\n    s->bytes_read = bytes;\n    s->pos_read = 0;\n    s->data_write = null;\n    s->bytes_write = 0;\n    s->pos_write = 0;\n}\n\nstatic void rt_streams_write_only(rt_stream_memory_if* s,\n        void* data, int64_t bytes) {\n    s->stream.read = null;\n    s->stream.write = rt_streams_memory_write;\n    s->data_read = null;\n    s->bytes_read = 0;\n    s->pos_read = 0;\n    s->data_write = data;\n    s->bytes_write = bytes;\n    s->pos_write = 0;\n}\n\nstatic void rt_streams_read_write(rt_stream_memory_if* s,\n        const void* read, int64_t read_bytes,\n        void* write, int64_t write_bytes) {\n    s->stream.read = rt_streams_memory_read;\n    s->stream.write = rt_streams_memory_write;\n    s->data_read = read;\n    s->bytes_read = read_bytes;\n    s->pos_read = 0;\n    s->pos_read = 0;\n    s->data_write = write;\n    s->bytes_write = write_bytes;\n    s->pos_write = 0;\n}\n\n#ifdef RT_TESTS\n\nstatic void rt_streams_test(void) {\n    {   // read test\n        uint8_t memory[256];\n        for (int32_t i = 0; i < rt_countof(memory); i++) { memory[i] = (uint8_t)i; }\n        for (int32_t i = 1; i < rt_countof(memory) - 1; i++) {\n            rt_stream_memory_if ms; // memory stream\n            rt_streams.read_only(&ms, memory, sizeof(memory));\n            uint8_t data[256];\n            for (int32_t j = 0; j < rt_countof(data); j++) { data[j] = 0xFF; }\n            int64_t transferred = 0;\n            errno_t r = ms.stream.read(&ms.stream, data, i, &transferred);\n            rt_swear(r == 0 && transferred == i);\n            for (int32_t j = 0; j < i; j++) { rt_swear(data[j] == memory[j]); }\n            for (int32_t j = i; j < rt_countof(data); j++) { rt_swear(data[j] == 0xFF); }\n        }\n    }\n    {   // write test\n        // TODO: implement\n    }\n    {   // read/write test\n        // TODO: implement\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_streams_test(void) { }\n\n#endif\n\nrt_streams_if rt_streams = {\n    .read_only  = rt_streams_read_only,\n    .write_only = rt_streams_write_only,\n    .read_write = rt_streams_read_write,\n    .test       = rt_streams_test\n};\n"
  },
  {
    "path": "src/rt/rt_threads.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\n// events:\n\nstatic rt_event_t rt_event_create(void) {\n    HANDLE e = CreateEvent(null, false, false, null);\n    rt_not_null(e);\n    return (rt_event_t)e;\n}\n\nstatic rt_event_t rt_event_create_manual(void) {\n    HANDLE e = CreateEvent(null, true, false, null);\n    rt_not_null(e);\n    return (rt_event_t)e;\n}\n\nstatic void rt_event_set(rt_event_t e) {\n    rt_fatal_win32err(SetEvent((HANDLE)e));\n}\n\nstatic void rt_event_reset(rt_event_t e) {\n    rt_fatal_win32err(ResetEvent((HANDLE)e));\n}\n\nstatic int32_t rt_event_wait_or_timeout(rt_event_t e, fp64_t seconds) {\n    uint32_t ms = seconds < 0 ? INFINITE : (uint32_t)(seconds * 1000.0 + 0.5);\n    DWORD i = WaitForSingleObject(e, ms);\n    rt_swear(i != WAIT_FAILED, \"i: %d\", i);\n    errno_t r = rt_wait_ix2e(i);\n    if (r != 0) { rt_swear(i == WAIT_TIMEOUT || i == WAIT_ABANDONED); }\n    return i == WAIT_TIMEOUT ? -1 : (i == WAIT_ABANDONED ? -2 : i);\n}\n\nstatic void rt_event_wait(rt_event_t e) { rt_event_wait_or_timeout(e, -1); }\n\nstatic int32_t rt_event_wait_any_or_timeout(int32_t n,\n        rt_event_t events[], fp64_t s) {\n    rt_swear(n < 64); // Win32 API limit\n    const uint32_t ms = s < 0 ? INFINITE : (uint32_t)(s * 1000.0 + 0.5);\n    const HANDLE* es = (const HANDLE*)events;\n    DWORD i = WaitForMultipleObjects((DWORD)n, es, false, ms);\n    rt_swear(i != WAIT_FAILED, \"i: %d\", i);\n    errno_t r = rt_wait_ix2e(i);\n    if (r != 0) { rt_swear(i == WAIT_TIMEOUT || i == WAIT_ABANDONED); }\n    return i == WAIT_TIMEOUT ? -1 : (i == WAIT_ABANDONED ? -2 : i);\n}\n\nstatic int32_t rt_event_wait_any(int32_t n, rt_event_t e[]) {\n    return rt_event_wait_any_or_timeout(n, e, -1);\n}\n\nstatic void rt_event_dispose(rt_event_t h) {\n    rt_win32_close_handle(h);\n}\n\n#ifdef RT_TESTS\n\n// test:\n\n// check if the elapsed time is within the expected range\nstatic void rt_event_test_check_time(fp64_t start, fp64_t expected) {\n    fp64_t elapsed = rt_clock.seconds() - start;\n    // Old Windows scheduler is prone to 2x16.6ms ~ 33ms delays (observed)\n    rt_swear(elapsed >= expected - 0.04 && elapsed <= expected + 0.250,\n          \"expected: %f elapsed %f seconds\", expected, elapsed);\n}\n\nstatic void rt_event_test(void) {\n    rt_event_t event = rt_event.create();\n    fp64_t start = rt_clock.seconds();\n    rt_event.set(event);\n    rt_event.wait(event);\n    rt_event_test_check_time(start, 0); // Event should be immediate\n    rt_event.reset(event);\n    start = rt_clock.seconds();\n    const fp64_t timeout_seconds = 1.0 / 8.0;\n    int32_t result = rt_event.wait_or_timeout(event, timeout_seconds);\n    rt_event_test_check_time(start, timeout_seconds);\n    rt_swear(result == -1); // Timeout expected\n    enum { count = 5 };\n    rt_event_t events[count];\n    for (int32_t i = 0; i < rt_countof(events); i++) {\n        events[i] = rt_event.create_manual();\n    }\n    start = rt_clock.seconds();\n    rt_event.set(events[2]); // Set the third event\n    int32_t index = rt_event.wait_any(rt_countof(events), events);\n    rt_swear(index == 2);\n    rt_event_test_check_time(start, 0);\n    rt_swear(index == 2); // Third event should be triggered\n    rt_event.reset(events[2]); // Reset the third event\n    start = rt_clock.seconds();\n    result = rt_event.wait_any_or_timeout(rt_countof(events), events, timeout_seconds);\n    rt_swear(result == -1);\n    rt_event_test_check_time(start, timeout_seconds);\n    rt_swear(result == -1); // Timeout expected\n    // Clean up\n    rt_event.dispose(event);\n    for (int32_t i = 0; i < rt_countof(events); i++) {\n        rt_event.dispose(events[i]);\n    }\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_event_test(void) { }\n\n#endif\n\nrt_event_if rt_event = {\n    .create              = rt_event_create,\n    .create_manual       = rt_event_create_manual,\n    .set                 = rt_event_set,\n    .reset               = rt_event_reset,\n    .wait                = rt_event_wait,\n    .wait_or_timeout     = rt_event_wait_or_timeout,\n    .wait_any            = rt_event_wait_any,\n    .wait_any_or_timeout = rt_event_wait_any_or_timeout,\n    .dispose             = rt_event_dispose,\n    .test                = rt_event_test\n};\n\n// mutexes:\n\nrt_static_assertion(sizeof(CRITICAL_SECTION) == sizeof(rt_mutex_t));\n\nstatic void rt_mutex_init(rt_mutex_t* m) {\n    CRITICAL_SECTION* cs = (CRITICAL_SECTION*)m;\n    rt_fatal_win32err(InitializeCriticalSectionAndSpinCount(cs, 4096));\n}\n\nstatic void rt_mutex_lock(rt_mutex_t* m) {\n    EnterCriticalSection((CRITICAL_SECTION*)m);\n}\n\nstatic void rt_mutex_unlock(rt_mutex_t* m) {\n    LeaveCriticalSection((CRITICAL_SECTION*)m);\n}\n\nstatic void rt_mutex_dispose(rt_mutex_t* m) {\n    DeleteCriticalSection((CRITICAL_SECTION*)m);\n}\n\n// test:\n\n// check if the elapsed time is within the expected range\nstatic void rt_mutex_test_check_time(fp64_t start, fp64_t expected) {\n    fp64_t elapsed = rt_clock.seconds() - start;\n    // Old Windows scheduler is prone to 2x16.6ms ~ 33ms delays\n    rt_swear(elapsed >= expected - 0.04 && elapsed <= expected + 0.04,\n          \"expected: %f elapsed %f seconds\", expected, elapsed);\n}\n\nstatic void rt_mutex_test_lock_unlock(void* arg) {\n    rt_mutex_t* mutex = (rt_mutex_t*)arg;\n    rt_mutex.lock(mutex);\n    rt_thread.sleep_for(0.01); // Hold the mutex for 10ms\n    rt_mutex.unlock(mutex);\n}\n\nstatic void rt_mutex_test(void) {\n    rt_mutex_t mutex;\n    rt_mutex.init(&mutex);\n    fp64_t start = rt_clock.seconds();\n    rt_mutex.lock(&mutex);\n    rt_mutex.unlock(&mutex);\n    // Lock and unlock should be immediate\n    rt_mutex_test_check_time(start, 0);\n    enum { count = 5 };\n    rt_thread_t ts[count];\n    for (int32_t i = 0; i < rt_countof(ts); i++) {\n        ts[i] = rt_thread.start(rt_mutex_test_lock_unlock, &mutex);\n    }\n    // Wait for all threads to finish\n    for (int32_t i = 0; i < rt_countof(ts); i++) {\n        rt_thread.join(ts[i], -1);\n    }\n    rt_mutex.dispose(&mutex);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\nrt_mutex_if rt_mutex = {\n    .init    = rt_mutex_init,\n    .lock    = rt_mutex_lock,\n    .unlock  = rt_mutex_unlock,\n    .dispose = rt_mutex_dispose,\n    .test    = rt_mutex_test\n};\n\n// threads:\n\nstatic void* rt_thread_ntdll(void) {\n    static HMODULE ntdll;\n    if (ntdll == null) {\n        ntdll = (void*)GetModuleHandleA(\"ntdll.dll\");\n    }\n    if (ntdll == null) {\n        ntdll = rt_loader.open(\"ntdll.dll\", 0);\n    }\n    rt_not_null(ntdll);\n    return ntdll;\n}\n\nstatic fp64_t rt_thread_ns2ms(int64_t ns) {\n    return (fp64_t)ns / (fp64_t)rt_clock.nsec_in_msec;\n}\n\nstatic void rt_thread_set_timer_resolution(uint64_t nanoseconds) {\n    typedef int32_t (*query_timer_resolution_t)(ULONG* minimum_resolution,\n        ULONG* maximum_resolution, ULONG* actual_resolution);\n    typedef int32_t (*set_timer_resolution_t)(ULONG requested_resolution,\n        BOOLEAN set, ULONG* actual_resolution); // ntdll.dll\n    void* nt_dll = rt_thread_ntdll();\n    query_timer_resolution_t query_timer_resolution =  (query_timer_resolution_t)\n        rt_loader.sym(nt_dll, \"NtQueryTimerResolution\");\n    set_timer_resolution_t set_timer_resolution = (set_timer_resolution_t)\n        rt_loader.sym(nt_dll, \"NtSetTimerResolution\");\n    unsigned long min100ns = 16 * 10 * 1000;\n    unsigned long max100ns =  1 * 10 * 1000;\n    unsigned long cur100ns =  0;\n    rt_fatal_if(query_timer_resolution(&min100ns, &max100ns, &cur100ns) != 0);\n    uint64_t max_ns = max100ns * 100uLL;\n//  uint64_t min_ns = min100ns * 100uLL;\n//  uint64_t cur_ns = cur100ns * 100uLL;\n    // max resolution is lowest possible delay between timer events\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f\"\n//          \" ms (milliseconds)\",\n//          rt_thread_ns2ms(min_ns),\n//          rt_thread_ns2ms(max_ns),\n//          rt_thread_ns2ms(cur_ns));\n//  }\n    // note that maximum resolution is actually < minimum\n    nanoseconds = rt_max(max_ns, nanoseconds);\n    unsigned long ns = (unsigned long)((nanoseconds + 99) / 100);\n    rt_fatal_if(set_timer_resolution(ns, true, &cur100ns) != 0);\n    rt_fatal_if(query_timer_resolution(&min100ns, &max100ns, &cur100ns) != 0);\n//  if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//      min_ns = min100ns * 100uLL;\n//      max_ns = max100ns * 100uLL; // the smallest interval\n//      cur_ns = cur100ns * 100uLL;\n//      rt_println(\"timer resolution min: %.3f max: %.3f cur: %.3f ms (milliseconds)\",\n//          rt_thread_ns2ms(min_ns),\n//          rt_thread_ns2ms(max_ns),\n//          rt_thread_ns2ms(cur_ns));\n//  }\n}\n\nstatic void rt_thread_power_throttling_disable_for_process(void) {\n    static bool disabled_for_the_process;\n    if (!disabled_for_the_process) {\n        PROCESS_POWER_THROTTLING_STATE pt = { 0 };\n        pt.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;\n        pt.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED;\n        pt.StateMask = 0;\n        rt_fatal_win32err(SetProcessInformation(GetCurrentProcess(),\n            ProcessPowerThrottling, &pt, sizeof(pt)));\n        // PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION\n        // does not work on Win10. There is no easy way to\n        // distinguish Windows 11 from 10 (Microsoft great engineering)\n        pt.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;\n        pt.StateMask = 0;\n        // ignore error on Windows 10:\n        (void)SetProcessInformation(GetCurrentProcess(),\n            ProcessPowerThrottling, &pt, sizeof(pt));\n        disabled_for_the_process = true;\n    }\n}\n\nstatic void rt_thread_power_throttling_disable_for_thread(HANDLE thread) {\n    THREAD_POWER_THROTTLING_STATE pt = { 0 };\n    pt.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;\n    pt.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;\n    pt.StateMask = 0;\n    rt_fatal_win32err(SetThreadInformation(thread,\n        ThreadPowerThrottling, &pt, sizeof(pt)));\n}\n\nstatic void rt_thread_disable_power_throttling(void) {\n    rt_thread_power_throttling_disable_for_process();\n    rt_thread_power_throttling_disable_for_thread(GetCurrentThread());\n}\n\nstatic const char* rt_thread_rel2str(int32_t rel) {\n    switch (rel) {\n        case RelationProcessorCore   : return \"ProcessorCore   \";\n        case RelationNumaNode        : return \"NumaNode        \";\n        case RelationCache           : return \"Cache           \";\n        case RelationProcessorPackage: return \"ProcessorPackage\";\n        case RelationGroup           : return \"Group           \";\n        case RelationProcessorDie    : return \"ProcessorDie    \";\n        case RelationNumaNodeEx      : return \"NumaNodeEx      \";\n        case RelationProcessorModule : return \"ProcessorModule \";\n        default: rt_assert(false, \"fix me\"); return \"???\";\n    }\n}\n\nstatic uint64_t rt_thread_next_physical_processor_affinity_mask(void) {\n    static volatile int32_t initialized;\n    static int32_t init;\n    static int32_t next = 1; // next physical core to use\n    static int32_t cores = 0; // number of physical processors (cores)\n    static uint64_t any;\n    static uint64_t affinity[64]; // mask for each physical processor\n    bool set_to_true = rt_atomics.compare_exchange_int32(&init, false, true);\n    if (set_to_true) {\n        // Concept D: 6 cores, 12 logical processors: 27 lpi entries\n        static SYSTEM_LOGICAL_PROCESSOR_INFORMATION lpi[64];\n        DWORD bytes = 0;\n        GetLogicalProcessorInformation(null, &bytes);\n        rt_assert(bytes % sizeof(lpi[0]) == 0);\n        // number of lpi entries == 27 on 6 core / 12 logical processors system\n        int32_t n = bytes / sizeof(lpi[0]);\n        rt_assert(bytes <= sizeof(lpi), \"increase lpi[%d]\", n);\n        rt_fatal_win32err(GetLogicalProcessorInformation(&lpi[0], &bytes));\n        for (int32_t i = 0; i < n; i++) {\n//          if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n//              rt_println(\"[%2d] affinity mask 0x%016llX relationship=%d %s\", i,\n//                  lpi[i].ProcessorMask, lpi[i].Relationship,\n//                  rt_thread_rel2str(lpi[i].Relationship));\n//          }\n            if (lpi[i].Relationship == RelationProcessorCore) {\n                rt_assert(cores < rt_countof(affinity), \"increase affinity[%d]\", cores);\n                if (cores < rt_countof(affinity)) {\n                    any |= lpi[i].ProcessorMask;\n                    affinity[cores] = lpi[i].ProcessorMask;\n                    cores++;\n                }\n            }\n        }\n        initialized = true;\n    } else {\n        while (initialized == 0) { rt_thread.sleep_for(1 / 1024.0); }\n        rt_assert(any != 0); // should not ever happen\n        if (any == 0) { any = (uint64_t)(-1LL); }\n    }\n    uint64_t mask = next < cores ? affinity[next] : any;\n    rt_assert(mask != 0);\n    // assume last physical core is least popular\n    if (next < cores) { next++; } // not circular\n    return mask;\n}\n\nstatic void rt_thread_realtime(void) {\n    rt_fatal_win32err(SetPriorityClass(GetCurrentProcess(),\n        REALTIME_PRIORITY_CLASS));\n    rt_fatal_win32err(SetThreadPriority(GetCurrentThread(),\n        THREAD_PRIORITY_TIME_CRITICAL));\n    rt_fatal_win32err(SetThreadPriorityBoost(GetCurrentThread(),\n        /* bDisablePriorityBoost = */ false));\n    // desired: 0.5ms = 500us (microsecond) = 50,000ns\n    rt_thread_set_timer_resolution((uint64_t)rt_clock.nsec_in_usec * 500ULL);\n    rt_fatal_win32err(SetThreadAffinityMask(GetCurrentThread(),\n        rt_thread_next_physical_processor_affinity_mask()));\n    rt_thread_disable_power_throttling();\n}\n\nstatic void rt_thread_yield(void) { SwitchToThread(); }\n\nstatic rt_thread_t rt_thread_start(void (*func)(void*), void* p) {\n    rt_thread_t t = (rt_thread_t)CreateThread(null, 0,\n        (LPTHREAD_START_ROUTINE)(void*)func, p, 0, null);\n    rt_not_null(t);\n    return t;\n}\n\nstatic bool is_handle_valid(void* h) {\n    DWORD flags = 0;\n    return GetHandleInformation(h, &flags);\n}\n\nstatic errno_t rt_thread_join(rt_thread_t t, fp64_t timeout) {\n    rt_not_null(t);\n    rt_fatal_if(!is_handle_valid(t));\n    const uint32_t ms = timeout < 0 ? INFINITE : (uint32_t)(timeout * 1000.0 + 0.5);\n    DWORD ix = WaitForSingleObject(t, (DWORD)ms);\n    errno_t r = rt_wait_ix2e(ix);\n    rt_assert(r != ERROR_REQUEST_ABORTED, \"AFAIK thread can`t be ABANDONED\");\n    if (r == 0) {\n        rt_win32_close_handle(t);\n    } else {\n        rt_println(\"failed to join thread %p %s\", t, rt_strerr(r));\n    }\n    return r;\n}\n\nstatic void rt_thread_detach(rt_thread_t t) {\n    rt_not_null(t);\n    rt_fatal_if(!is_handle_valid(t));\n    rt_win32_close_handle(t);\n}\n\nstatic void rt_thread_name(const char* name) {\n    uint16_t stack[128];\n    rt_fatal_if(rt_str.len(name) >= rt_countof(stack), \"name too long: %s\", name);\n    rt_str.utf8to16(stack, rt_countof(stack), name, -1);\n    HRESULT r = SetThreadDescription(GetCurrentThread(), stack);\n    // notoriously returns 0x10000000 for no good reason whatsoever\n    rt_fatal_if(!SUCCEEDED(r));\n}\n\nstatic void rt_thread_sleep_for(fp64_t seconds) {\n    rt_assert(seconds >= 0);\n    if (seconds < 0) { seconds = 0; }\n    int64_t ns100 = (int64_t)(seconds * 1.0e+7); // in 0.1 us aka 100ns\n    typedef int32_t (__stdcall *nt_delay_execution_t)(BOOLEAN alertable,\n        PLARGE_INTEGER DelayInterval);\n    static nt_delay_execution_t NtDelayExecution;\n    // delay in 100-ns units. negative value means delay relative to current.\n    LARGE_INTEGER delay = {0}; // delay in 100-ns units.\n    delay.QuadPart = -ns100; // negative value means delay relative to current.\n    if (NtDelayExecution == null) {\n        void* ntdll = rt_thread_ntdll();\n        NtDelayExecution = (nt_delay_execution_t)\n            rt_loader.sym(ntdll, \"NtDelayExecution\");\n        rt_not_null(NtDelayExecution);\n    }\n    // If \"alertable\" is set, sleep_for() can break earlier\n    // as a result of NtAlertThread call.\n    NtDelayExecution(false, &delay);\n}\n\nstatic uint64_t rt_thread_id_of(rt_thread_t t) {\n    return (uint64_t)GetThreadId((HANDLE)t);\n}\n\nstatic uint64_t rt_thread_id(void) {\n    return (uint64_t)GetThreadId(GetCurrentThread());\n}\n\nstatic rt_thread_t rt_thread_self(void) {\n    // GetCurrentThread() returns pseudo-handle, not a real handle\n    // if real handle is ever needed may do\n    // rt_thread_t t = rt_thread.open(rt_thread.id()) and\n    // rt_thread.close(t) instead.\n    return (rt_thread_t)GetCurrentThread();\n}\n\nstatic errno_t rt_thread_open(rt_thread_t *t, uint64_t id) {\n    // GetCurrentThread() returns pseudo-handle, not a real handle.\n    // if real handle is ever needed do rt_thread_id_of() instead\n    // but don't forget to do rt_thread.close() after that.\n    *t = (rt_thread_t)OpenThread(THREAD_ALL_ACCESS, false, (DWORD)id);\n    return *t == null ? rt_core.err() : 0;\n}\n\nstatic void rt_thread_close(rt_thread_t t) {\n    rt_not_null(t);\n    rt_win32_close_handle((HANDLE)t);\n}\n\n#ifdef RT_TESTS\n\n// test: https://en.wikipedia.org/wiki/Dining_philosophers_problem\n\ntypedef struct rt_thread_philosophers_s rt_thread_philosophers_t;\n\ntypedef struct {\n    rt_thread_philosophers_t* ps;\n    rt_mutex_t  fork;\n    rt_mutex_t* left_fork;\n    rt_mutex_t* right_fork;\n    rt_thread_t thread;\n    uint64_t    id;\n} rt_thread_philosopher_t;\n\ntypedef struct rt_thread_philosophers_s {\n    rt_thread_philosopher_t philosopher[3];\n    rt_event_t fed_up[3];\n    uint32_t seed;\n    volatile bool enough;\n} rt_thread_philosophers_t;\n\n#pragma push_macro(\"verbose\") // --verbosity trace\n\n#define verbose(...) do {                                 \\\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) { \\\n        rt_println(__VA_ARGS__);                             \\\n    }                                                     \\\n} while (0)\n\nstatic void rt_thread_philosopher_think(rt_thread_philosopher_t* p) {\n    verbose(\"philosopher %d is thinking.\", p->id);\n    // Random think time between .1 and .3 seconds\n    fp64_t seconds = (rt_num.random32(&p->ps->seed) % 30 + 1) / 100.0;\n    rt_thread.sleep_for(seconds);\n}\n\nstatic void rt_thread_philosopher_eat(rt_thread_philosopher_t* p) {\n    verbose(\"philosopher %d is eating.\", p->id);\n    // Random eat time between .1 and .2 seconds\n    fp64_t seconds = (rt_num.random32(&p->ps->seed) % 20 + 1) / 100.0;\n    rt_thread.sleep_for(seconds);\n}\n\n// To avoid deadlocks in the Three Philosophers problem, we can implement\n// the Tanenbaum's solution, which ensures that one of the philosophers\n// (e.g., the last one) tries to pick up the right fork first, while the\n// others pick up the left fork first. This breaks the circular wait\n// condition and prevents deadlock.\n\n// If the philosopher is the last one (p->id == n - 1) they will try to pick\n// up the right fork first and then the left fork. All other philosophers will\n// pick up the left fork first and then the right fork, as before. This change\n// ensures that at least one philosopher will be able to eat, breaking the\n// circular wait condition and preventing deadlock.\n\nstatic void rt_thread_philosopher_routine(void* arg) {\n    rt_thread_philosopher_t* p = (rt_thread_philosopher_t*)arg;\n    enum { n = rt_countof(p->ps->philosopher) };\n    rt_thread.name(\"philosopher\");\n    rt_thread.realtime();\n    while (!p->ps->enough) {\n        rt_thread_philosopher_think(p);\n        if (p->id == n - 1) { // Last philosopher picks up the right fork first\n            rt_mutex.lock(p->right_fork);\n            verbose(\"philosopher %d picked up right fork.\", p->id);\n            rt_mutex.lock(p->left_fork);\n            verbose(\"philosopher %d picked up left fork.\", p->id);\n        } else { // Other philosophers pick up the left fork first\n            rt_mutex.lock(p->left_fork);\n            verbose(\"philosopher %d picked up left fork.\", p->id);\n            rt_mutex.lock(p->right_fork);\n            verbose(\"philosopher %d picked up right fork.\", p->id);\n        }\n        rt_thread_philosopher_eat(p);\n        rt_mutex.unlock(p->right_fork);\n        verbose(\"philosopher %d put down right fork.\", p->id);\n        rt_mutex.unlock(p->left_fork);\n        verbose(\"philosopher %d put down left fork.\", p->id);\n        rt_event.set(p->ps->fed_up[p->id]);\n    }\n}\n\nstatic void rt_thread_detached_sleep(void* rt_unused(p)) {\n    rt_thread.sleep_for(1000.0); // seconds\n}\n\nstatic void rt_thread_detached_loop(void* rt_unused(p)) {\n    uint64_t sum = 0;\n    for (uint64_t i = 0; i < UINT64_MAX; i++) { sum += i; }\n    // make sure that compiler won't get rid of the loop:\n    rt_swear(sum == 0x8000000000000001ULL, \"sum: %llu 0x%16llX\", sum, sum);\n}\n\nstatic void rt_thread_test(void) {\n    rt_thread_philosophers_t ps = { .seed = 1 };\n    enum { n = rt_countof(ps.philosopher) };\n    // Initialize mutexes for forks\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        p->id = i;\n        p->ps = &ps;\n        rt_mutex.init(&p->fork);\n        p->left_fork = &p->fork;\n        ps.fed_up[i] = rt_event.create();\n    }\n    // Create and start philosopher threads\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_thread_philosopher_t* r = &ps.philosopher[(i + 1) % n];\n        p->right_fork = r->left_fork;\n        p->thread = rt_thread.start(rt_thread_philosopher_routine, p);\n    }\n    // wait for all philosophers being fed up:\n    for (int32_t i = 0; i < n; i++) { rt_event.wait(ps.fed_up[i]); }\n    ps.enough = true;\n    // join all philosopher threads\n    for (int32_t i = 0; i < n; i++) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_thread.join(p->thread, -1);\n    }\n    // Dispose of mutexes and events\n    for (int32_t i = 0; i < n; ++i) {\n        rt_thread_philosopher_t* p = &ps.philosopher[i];\n        rt_mutex.dispose(&p->fork);\n        rt_event.dispose(ps.fed_up[i]);\n    }\n    // detached threads are hacky and not that swell of an idea\n    // but sometimes can be useful for 1. quick hacks 2. threads\n    // that execute blocking calls that e.g. write logs to the\n    // internet service that hangs.\n    // test detached threads\n    rt_thread_t detached_sleep = rt_thread.start(rt_thread_detached_sleep, null);\n    rt_thread.detach(detached_sleep);\n    rt_thread_t detached_loop = rt_thread.start(rt_thread_detached_loop, null);\n    rt_thread.detach(detached_loop);\n    // leave detached threads sleeping and running till ExitProcess(0)\n    // that should NOT hang.\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"verbose\")\n\n#else\nstatic void rt_thread_test(void) { }\n#endif\n\nrt_thread_if rt_thread = {\n    .start     = rt_thread_start,\n    .join      = rt_thread_join,\n    .detach    = rt_thread_detach,\n    .name      = rt_thread_name,\n    .realtime  = rt_thread_realtime,\n    .yield     = rt_thread_yield,\n    .sleep_for = rt_thread_sleep_for,\n    .id_of     = rt_thread_id_of,\n    .id        = rt_thread_id,\n    .self      = rt_thread_self,\n    .open      = rt_thread_open,\n    .close     = rt_thread_close,\n    .test      = rt_thread_test\n};\n"
  },
  {
    "path": "src/rt/rt_vigil.c",
    "content": "#include \"rt/rt.h\"\n#include <stdio.h>\n#include <string.h>\n\nstatic void rt_vigil_breakpoint_and_abort(void) {\n    rt_debug.breakpoint(); // only if debugger is present\n    rt_debug.raise(rt_debug.exception.noncontinuable);\n    rt_core.abort();\n}\n\nstatic int32_t rt_vigil_failed_assertion(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_debug.println_va(file, line, func, format, va);\n    va_end(va);\n    rt_debug.println(file, line, func, \"assertion failed: %s\\n\", condition);\n    // avoid warnings: conditional expression always true and unreachable code\n    const bool always_true = rt_core.abort != null;\n    if (always_true) { rt_vigil_breakpoint_and_abort(); }\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_termination_va(const char* file, int32_t line,\n        const char* func, const char* condition, errno_t r,\n        const char* format, va_list va) {\n    const int32_t er = rt_core.err();\n    const int32_t en = errno;\n    rt_debug.println_va(file, line, func, format, va);\n    if (r != er && r != 0) {\n        rt_debug.perror(file, line, func, r, \"\");\n    }\n    // report last errors:\n    if (er != 0) { rt_debug.perror(file, line, func, er, \"\"); }\n    if (en != 0) { rt_debug.perrno(file, line, func, en, \"\"); }\n    if (condition != null && condition[0] != 0) {\n        rt_debug.println(file, line, func, \"FATAL: %s\\n\", condition);\n    } else {\n        rt_debug.println(file, line, func, \"FATAL\\n\");\n    }\n    const bool always_true = rt_core.abort != null;\n    if (always_true) { rt_vigil_breakpoint_and_abort(); }\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_termination(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    rt_vigil_fatal_termination_va(file, line, func, condition, 0, format, va);\n    va_end(va);\n    return 0;\n}\n\nstatic int32_t rt_vigil_fatal_if_error(const char* file, int32_t line,\n    const char* func, const char* condition, errno_t r,\n    const char* format, ...) {\n    if (r != 0) {\n        va_list va;\n        va_start(va, format);\n        rt_vigil_fatal_termination_va(file, line, func, condition, r, format, va);\n        va_end(va);\n    }\n    return 0;\n}\n\n#ifdef RT_TESTS\n\nstatic rt_vigil_if  rt_vigil_test_saved;\nstatic int32_t      rt_vigil_test_failed_assertion_count;\n\n#pragma push_macro(\"rt_vigil\")\n// intimate knowledge of vigil.*() functions used in macro definitions\n#define rt_vigil rt_vigil_test_saved\n\nstatic int32_t rt_vigil_test_failed_assertion(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    rt_fatal_if_not(strcmp(file,  __FILE__) == 0, \"file: %s\", file);\n    rt_fatal_if_not(line > __LINE__, \"line: %s\", line);\n    rt_assert(strcmp(func, \"rt_vigil_test\") == 0, \"func: %s\", func);\n    rt_fatal_if(condition == null || condition[0] == 0);\n    rt_fatal_if(format == null || format[0] == 0);\n    rt_vigil_test_failed_assertion_count++;\n    if (rt_debug.verbosity.level >= rt_debug.verbosity.trace) {\n        va_list va;\n        va_start(va, format);\n        rt_debug.println_va(file, line, func, format, va);\n        va_end(va);\n        rt_debug.println(file, line, func, \"assertion failed: %s (expected)\\n\",\n                     condition);\n    }\n    return 0;\n}\n\nstatic int32_t rt_vigil_test_fatal_calls_count;\n\nstatic int32_t rt_vigil_test_fatal_termination(const char* file, int32_t line,\n        const char* func, const char* condition, const char* format, ...) {\n    const int32_t er = rt_core.err();\n    const int32_t en = errno;\n    rt_assert(er == 2, \"rt_core.err: %d expected 2\", er);\n    rt_assert(en == 2, \"errno: %d expected 2\", en);\n    rt_fatal_if_not(strcmp(file,  __FILE__) == 0, \"file: %s\", file);\n    rt_fatal_if_not(line > __LINE__, \"line: %s\", line);\n    rt_assert(strcmp(func, \"rt_vigil_test\") == 0, \"func: %s\", func);\n    rt_assert(strcmp(condition, \"\") == 0); // not yet used expected to be \"\"\n    rt_assert(format != null && format[0] != 0);\n    rt_vigil_test_fatal_calls_count++;\n    if (rt_debug.verbosity.level > rt_debug.verbosity.trace) {\n        va_list va;\n        va_start(va, format);\n        rt_debug.println_va(file, line, func, format, va);\n        va_end(va);\n        if (er != 0) { rt_debug.perror(file, line, func, er, \"\"); }\n        if (en != 0) { rt_debug.perrno(file, line, func, en, \"\"); }\n        if (condition != null && condition[0] != 0) {\n            rt_debug.println(file, line, func, \"FATAL: %s (testing)\\n\", condition);\n        } else {\n            rt_debug.println(file, line, func, \"FATAL (testing)\\n\");\n        }\n    }\n    return 0;\n}\n\n#pragma pop_macro(\"rt_vigil\")\n\nstatic void rt_vigil_test(void) {\n    rt_vigil_test_saved = rt_vigil;\n    int32_t en = errno;\n    int32_t er = rt_core.err();\n    errno = 2; // ENOENT\n    rt_core.set_err(2); // ERROR_FILE_NOT_FOUND\n    rt_vigil.failed_assertion  = rt_vigil_test_failed_assertion;\n    rt_vigil.fatal_termination = rt_vigil_test_fatal_termination;\n    int32_t count = rt_vigil_test_fatal_calls_count;\n    rt_fatal(\"testing: %s call\", \"fatal()\");\n    rt_assert(rt_vigil_test_fatal_calls_count == count + 1);\n    count = rt_vigil_test_failed_assertion_count;\n    rt_assert(false, \"testing: rt_assert(%s)\", \"false\");\n    #ifdef DEBUG // verify that rt_assert() is only compiled in DEBUG:\n        rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count + 1);\n    #else // not RELEASE buid:\n        rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count);\n    #endif\n    count = rt_vigil_test_failed_assertion_count;\n    rt_swear(false, \"testing: swear(%s)\", \"false\");\n    // swear() is triggered in both debug and release configurations:\n    rt_fatal_if_not(rt_vigil_test_failed_assertion_count == count + 1);\n    errno = en;\n    rt_core.set_err(er);\n    rt_vigil = rt_vigil_test_saved;\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#else\n\nstatic void rt_vigil_test(void) { }\n\n#endif\n\nrt_vigil_if rt_vigil = {\n    .failed_assertion  = rt_vigil_failed_assertion,\n    .fatal_termination = rt_vigil_fatal_termination,\n    .fatal_if_error    = rt_vigil_fatal_if_error,\n    .test              = rt_vigil_test\n};\n"
  },
  {
    "path": "src/rt/rt_win32.c",
    "content": "#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n\nvoid rt_win32_close_handle(void* h) {\n    #pragma warning(suppress: 6001) // shut up overzealous IntelliSense\n    rt_fatal_win32err(CloseHandle((HANDLE)h));\n}\n\n// WAIT_ABANDONED only reported for mutexes not events\n// WAIT_FAILED means event was invalid handle or was disposed\n// by another thread while the calling thread was waiting for it.\n\n/* translate ix to error */\nerrno_t rt_wait_ix2e(uint32_t r) {\n    const int32_t ix = (int32_t)r;\n    return (errno_t)(\n          (int32_t)WAIT_OBJECT_0 <= ix && ix <= WAIT_OBJECT_0 + 63 ? 0 :\n          (ix == WAIT_ABANDONED ? ERROR_REQUEST_ABORTED :\n            (ix == WAIT_TIMEOUT ? ERROR_TIMEOUT :\n              (ix == WAIT_FAILED) ? rt_core.err() : ERROR_INVALID_HANDLE\n            )\n          )\n    );\n}"
  },
  {
    "path": "src/rt/rt_work.c",
    "content": "#include \"rt/rt.h\"\n\nstatic void rt_work_queue_no_duplicates(rt_work_t* w) {\n    rt_work_t* e = w->queue->head;\n    bool found = false;\n    while (e != null && !found) {\n        found = e == w;\n        if (!found) { e = e->next; }\n    }\n    rt_swear(!found);\n}\n\nstatic void rt_work_queue_post(rt_work_t* w) {\n    rt_assert(w->queue != null && w != null && w->when >= 0.0);\n    rt_work_queue_t* q = w->queue;\n    rt_atomics.spinlock_acquire(&q->lock);\n    rt_work_queue_no_duplicates(w); // under lock\n    //  Enqueue in time sorted order least ->time first to save\n    //  time searching in fetching from queue which is more frequent.\n    rt_work_t* p = null;\n    rt_work_t* e = q->head;\n    while (e != null && e->when <= w->when) {\n        p = e;\n        e = e->next;\n    }\n    w->next = e;\n    bool head = p == null;\n    if (head) {\n        q->head = w;\n    } else {\n        p->next = w;\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    if (head && q->changed != null) { rt_event.set(q->changed); }\n}\n\nstatic void rt_work_queue_cancel(rt_work_t* w) {\n    rt_swear(!w->canceled && w->queue != null && w->queue->head != null);\n    rt_work_queue_t* q = w->queue;\n    rt_atomics.spinlock_acquire(&q->lock);\n    rt_work_t* p = null;\n    rt_work_t* e = q->head;\n    bool changed = false; // head changed\n    while (e != null && !w->canceled) {\n        if (e == w) {\n            changed = p == null;\n            if (changed) {\n                q->head = e->next;\n        } else {\n                p->next = e->next;\n            }\n            e->next = null;\n            e->canceled = true;\n        } else {\n            p = e;\n            e = e->next;\n        }\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    rt_swear(w->canceled);\n    if (w->done != null) { rt_event.set(w->done); }\n    if (changed && q->changed != null) { rt_event.set(q->changed); }\n}\n\nstatic void rt_work_queue_flush(rt_work_queue_t* q) {\n    while (q->head != null) { rt_work_queue.cancel(q->head); }\n}\n\nstatic bool rt_work_queue_get(rt_work_queue_t* q, rt_work_t* *r) {\n    rt_work_t* w = null;\n    rt_atomics.spinlock_acquire(&q->lock);\n    bool changed = q->head != null && q->head->when <= rt_clock.seconds();\n    if (changed) {\n        w = q->head;\n        q->head = w->next;\n        w->next = null;\n    }\n    rt_atomics.spinlock_release(&q->lock);\n    *r = w;\n    if (changed && q->changed != null) { rt_event.set(q->changed); }\n    return w != null;\n}\n\nstatic void rt_work_queue_call(rt_work_t* w) {\n    if (w->work != null) { w->work(w); }\n    if (w->done != null) { rt_event.set(w->done); }\n}\n\nstatic void rt_work_queue_dispatch(rt_work_queue_t* q) {\n    rt_work_t* w = null;\n    while (rt_work_queue.get(q, &w)) { rt_work_queue.call(w); }\n}\n\nrt_work_queue_if rt_work_queue = {\n    .post     = rt_work_queue_post,\n    .get      = rt_work_queue_get,\n    .call     = rt_work_queue_call,\n    .dispatch = rt_work_queue_dispatch,\n    .cancel   = rt_work_queue_cancel,\n    .flush    = rt_work_queue_flush\n};\n\nstatic void rt_worker_thread(void* p) {\n    rt_thread.name(\"worker\");\n    rt_worker_t* worker = (rt_worker_t*)p;\n    rt_work_queue_t* q = &worker->queue;\n    while (!worker->quit) {\n        rt_work_queue.dispatch(q);\n        fp64_t timeout = -1.0; // forever\n        rt_atomics.spinlock_acquire(&q->lock);\n        if (q->head != null) {\n            timeout = rt_max(0, q->head->when - rt_clock.seconds());\n        }\n        rt_atomics.spinlock_release(&q->lock);\n        // if another item is inserted into head after unlocking\n        // the `wake` event guaranteed to be signalled\n        if (!worker->quit && timeout != 0) {\n            rt_event.wait_or_timeout(worker->wake, timeout);\n        }\n    }\n    rt_work_queue.dispatch(q);\n}\n\nstatic void rt_worker_start(rt_worker_t* worker) {\n    rt_assert(worker->wake == null && !worker->quit);\n    worker->wake  = rt_event.create();\n    worker->queue = (rt_work_queue_t){\n        .head = null, .lock = 0, .changed = worker->wake\n    };\n    worker->thread = rt_thread.start(rt_worker_thread, worker);\n}\n\nstatic errno_t rt_worker_join(rt_worker_t* worker, fp64_t to) {\n    worker->quit = true;\n    rt_event.set(worker->wake);\n    errno_t r = rt_thread.join(worker->thread, to);\n    if (r == 0) {\n        rt_event.dispose(worker->wake);\n        worker->wake = null;\n        worker->thread = null;\n        worker->quit = false;\n        rt_swear(worker->queue.head == null);\n    }\n    return r;\n}\n\nstatic void rt_worker_post(rt_worker_t* worker, rt_work_t* w) {\n    rt_assert(!worker->quit && worker->wake != null && worker->thread != null);\n    w->queue = &worker->queue;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_worker_test(void);\n\nrt_worker_if rt_worker = {\n    .start = rt_worker_start,\n    .post  = rt_worker_post,\n    .join  = rt_worker_join,\n    .test  = rt_worker_test\n};\n\n#ifdef RT_TESTS\n\n// tests:\n\n// keep in mind that rt_println() may be blocking and is a subject\n// of \"astronomical\" wait state times in order of dozens of ms.\n\nstatic int32_t rt_test_called;\n\nstatic void rt_never_called(rt_work_t* rt_unused(w)) {\n    rt_test_called++;\n}\n\nstatic void rt_work_queue_test_1(void) {\n    rt_test_called = 0;\n    // testing insertion time ordering of two events into queue\n    const fp64_t now = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    rt_work_t c1 = {\n        .queue = &q,\n        .work = rt_never_called,\n        .when = now + 1.0\n    };\n    rt_work_t c2 = {\n        .queue = &q,\n        .work = rt_never_called,\n        .when = now + 0.5\n    };\n    rt_work_queue.post(&c1);\n    rt_swear(q.head == &c1 && q.head->next == null);\n    rt_work_queue.post(&c2);\n    rt_swear(q.head == &c2 && q.head->next == &c1);\n    rt_work_queue.flush(&q);\n    // test that canceled events are not dispatched\n    rt_swear(rt_test_called == 0 && c1.canceled && c2.canceled && q.head == null);\n    c1.canceled = false;\n    c2.canceled = false;\n    // test the rt_work_queue.cancel() function\n    rt_work_queue.post(&c1);\n    rt_work_queue.post(&c2);\n    rt_swear(q.head == &c2 && q.head->next == &c1);\n    rt_work_queue.cancel(&c2);\n    rt_swear(c2.canceled && q.head == &c1 && q.head->next == null);\n    c2.canceled = false;\n    rt_work_queue.post(&c2);\n    rt_work_queue.cancel(&c1);\n    rt_swear(c1.canceled && q.head == &c2 && q.head->next == null);\n    rt_work_queue.flush(&q);\n    rt_swear(rt_test_called == 0 && c1.canceled && c2.canceled && q.head == null);\n}\n\n// simple way of passing a single pointer to call_later\n\nstatic fp64_t rt_test_work_start; // makes timing debug traces easier to read\n\nstatic void rt_every_millisecond(rt_work_t* w) {\n    int32_t* i = (int32_t*)w->data;\n    fp64_t now = rt_clock.seconds();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.info) {\n        const fp64_t since_start = now - rt_test_work_start;\n        const fp64_t dt = w->when - rt_test_work_start;\n        rt_println(\"%d now: %.6f time: %.6f\", *i, since_start, dt);\n    }\n    (*i)++;\n    // read rt_clock.seconds() again because rt_println() above could block\n    w->when = rt_clock.seconds() + 0.001;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_work_queue_test_2(void) {\n    rt_thread.realtime();\n    rt_test_work_start = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    // if a single pointer will suffice\n    int32_t i = 0;\n    rt_work_t c = {\n        .queue = &q,\n        .work = rt_every_millisecond,\n        .when = rt_test_work_start + 0.001,\n        .data = &i\n    };\n    rt_work_queue.post(&c);\n    while (q.head != null && i < 8) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(&q);\n    }\n    rt_work_queue.flush(&q);\n    rt_swear(q.head == null);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) {\n        rt_println(\"called: %d times\", i);\n    }\n}\n\n// extending rt_work_t with extra data:\n\ntypedef struct rt_work_ex_s {\n    // nameless union opens up base fields into rt_work_ex_t\n    // it is not necessary at all\n    union {\n        rt_work_t base;\n        struct rt_work_s;\n    };\n    struct { int32_t a; int32_t b; } s;\n    int32_t i;\n} rt_work_ex_t;\n\nstatic void rt_every_other_millisecond(rt_work_t* w) {\n    rt_work_ex_t* ex = (rt_work_ex_t*)w;\n    fp64_t now = rt_clock.seconds();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.info) {\n        const fp64_t since_start = now - rt_test_work_start;\n        const fp64_t dt  = w->when - rt_test_work_start;\n        rt_println(\".i: %d .extra: {.a: %d .b: %d} now: %.6f time: %.6f\",\n                ex->i, ex->s.a, ex->s.b, since_start, dt);\n    }\n    ex->i++;\n    const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n    // read rt_clock.seconds() again because rt_println() above could block\n    w->when = rt_clock.seconds() + 0.002;\n    rt_work_queue.post(w);\n}\n\nstatic void rt_work_queue_test_3(void) {\n    rt_thread.realtime();\n    rt_static_assertion(offsetof(rt_work_ex_t, base) == 0);\n    const fp64_t now = rt_clock.seconds();\n    rt_work_queue_t q = {0};\n    rt_work_ex_t ex = {\n        .queue = &q,\n        .work = rt_every_other_millisecond,\n        .when = now + 0.002,\n        .s = { .a = 1, .b = 2 },\n        .i = 0\n    };\n    rt_work_queue.post(&ex.base);\n    while (q.head != null && ex.i < 8) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(&q);\n    }\n    rt_work_queue.flush(&q);\n    rt_swear(q.head == null);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) {\n        rt_println(\"called: %d times\", ex.i);\n    }\n}\n\nstatic void rt_work_queue_test(void) {\n    rt_work_queue_test_1();\n    rt_work_queue_test_2();\n    rt_work_queue_test_3();\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\nstatic int32_t rt_test_do_work_called;\n\nstatic void rt_test_do_work(rt_work_t* rt_unused(w)) {\n    rt_test_do_work_called++;\n}\n\nstatic void rt_worker_test(void) {\n//  uncomment one of the following lines to see the output\n//  rt_debug.verbosity.level = rt_debug.verbosity.info;\n//  rt_debug.verbosity.level = rt_debug.verbosity.verbose;\n    rt_work_queue_test(); // first test rt_work_queue\n    rt_worker_t worker = { 0 };\n    rt_worker.start(&worker);\n    rt_work_t asap = {\n        .when = 0, // A.S.A.P.\n        .done = rt_event.create(),\n        .work = rt_test_do_work\n    };\n    rt_work_t later = {\n        .when = rt_clock.seconds() + 0.010, // 10ms\n        .done = rt_event.create(),\n        .work = rt_test_do_work\n    };\n    rt_worker.post(&worker, &asap);\n    rt_worker.post(&worker, &later);\n    // because `asap` and `later` are local variables\n    // code needs to wait for them to be processed inside\n    // this function before they goes out of scope\n    rt_event.wait(asap.done); // await(asap)\n    rt_event.dispose(asap.done); // responsibility of the caller\n    // wait for later:\n    rt_event.wait(later.done); // await(later)\n    rt_event.dispose(later.done); // responsibility of the caller\n    // quit the worker thread:\n    rt_fatal_if_error(rt_worker.join(&worker, -1.0));\n    // does worker respect .when dispatch time?\n    rt_swear(rt_clock.seconds() >= later.when);\n}\n\n#else\n\nstatic void rt_work_queue_test(void) {}\nstatic void rt_worker_test(void) {}\n\n#endif\n"
  },
  {
    "path": "src/samples/edit.test.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n\nstatic const char* lorem_ipsum_words[] = {\n    \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\",\n    \"elit\", \"quisque\", \"faucibus\", \"ex\", \"sapien\", \"vitae\", \"pellentesque\",\n    \"sem\", \"placerat\", \"in\", \"id\", \"cursus\", \"mi\", \"pretium\", \"tellus\",\n    \"duis\", \"convallis\", \"tempus\", \"leo\", \"eu\", \"aenean\", \"sed\", \"diam\",\n    \"urna\", \"tempor\", \"pulvinar\", \"vivamus\", \"fringilla\", \"lacus\", \"nec\",\n    \"metus\", \"bibendum\", \"egestas\", \"iaculis\", \"massa\", \"nisl\",\n    \"malesuada\", \"lacinia\", \"integer\", \"nunc\", \"posuere\", \"ut\", \"hendrerit\",\n    \"semper\", \"vel\", \"class\", \"aptent\", \"taciti\", \"sociosqu\", \"ad\", \"litora\",\n    \"torquent\", \"per\", \"conubia\", \"nostra\", \"inceptos\",\n    \"himenaeos\", \"orci\", \"varius\", \"natoque\", \"penatibus\", \"et\", \"magnis\",\n    \"dis\", \"parturient\", \"montes\", \"nascetur\", \"ridiculus\", \"mus\", \"donec\",\n    \"rhoncus\", \"eros\", \"lobortis\", \"nulla\", \"molestie\", \"mattis\",\n    \"scelerisque\", \"maximus\", \"eget\", \"fermentum\", \"odio\", \"phasellus\",\n    \"non\", \"purus\", \"est\", \"efficitur\", \"laoreet\", \"mauris\", \"pharetra\",\n    \"vestibulum\", \"fusce\", \"dictum\", \"risus\", \"blandit\", \"quis\",\n    \"suspendisse\", \"aliquet\", \"nisi\", \"sodales\", \"consequat\", \"magna\",\n    \"ante\", \"condimentum\", \"neque\", \"at\", \"luctus\", \"nibh\", \"finibus\",\n    \"facilisis\", \"dapibus\", \"etiam\", \"interdum\", \"tortor\", \"ligula\",\n    \"congue\", \"sollicitudin\", \"erat\", \"viverra\", \"ac\", \"tincidunt\", \"nam\",\n    \"porta\", \"elementum\", \"a\", \"enim\", \"euismod\", \"quam\", \"justo\",\n    \"lectus\", \"commodo\", \"augue\", \"arcu\", \"dignissim\", \"velit\", \"aliquam\",\n    \"imperdiet\", \"mollis\", \"nullam\", \"volutpat\", \"porttitor\",\n    \"ullamcorper\", \"rutrum\", \"gravida\", \"cras\", \"eleifend\", \"turpis\",\n    \"fames\", \"primis\", \"vulputate\", \"ornare\", \"sagittis\", \"vehicula\",\n    \"praesent\", \"dui\", \"felis\", \"venenatis\", \"ultrices\", \"proin\", \"libero\",\n    \"feugiat\", \"tristique\", \"accumsan\", \"maecenas\", \"potenti\", \"ultricies\",\n    \"habitant\", \"morbi\", \"senectus\", \"netus\", \"suscipit\", \"auctor\",\n    \"curabitur\", \"facilisi\", \"cubilia\", \"curae\", \"hac\", \"habitasse\",\n    \"platea\", \"dictumst\"\n};\n\n#define lorem_ipsum_canonique \\\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do \"         \\\n    \"eiusmod  tempor incididunt ut labore et dolore magna aliqua.Ut enim ad \"  \\\n    \"minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \" \\\n    \"ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \"      \\\n    \"voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur \"  \\\n    \"sint occaecat cupidatat non proident, sunt in culpa qui officia \"         \\\n    \"deserunt mollit anim id est laborum.\"\n\n#define lorem_ipsum_chinese \\\n    \"\\xE6\\x88\\x91\\xE6\\x98\\xAF\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE6\\x96\\x87\\xE6\\x9C\\xAC\\xE7\\x9A\\x84\\xE4\" \\\n    \"\\xBD\\x8D\\xE7\\xBD\\xAE\\xE3\\x80\\x82\\xE8\\xBF\\x99\\xE9\\x87\\x8C\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE4\\xBA\" \\\n    \"\\x86\\xE5\\x81\\x87\\xE6\\x96\\x87\\xE5\\x81\\x87\\xE5\\xAD\\x97\\xE3\\x80\\x82\\xE5\\xB8\\x8C\\xE6\\x9C\\x9B\" \\\n    \"\\xE8\\xBF\\x99\\xE4\\xBA\\x9B\\xE6\\x96\\x87\\xE5\\xAD\\x97\\xE5\\x8F\\xAF\\xE4\\xBB\\xA5\\xE5\\xA1\\xAB\\xE5\" \\\n    \"\\x85\\x85\\xE7\\xA9\\xBA\\xE7\\x99\\xBD\\xE3\\x80\\x82\";\n\n#define  lorem_ipsum_japanese \\\n    \"\\xE3\\x81\\x93\\xE3\\x82\\x8C\\xE3\\x81\\xAF\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\" \\\n    \"\\x82\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x81\\xA7\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE3\\x81\\x93\\xE3\\x81\" \\\n    \"\\x93\\xE3\\x81\\xAB\\xE6\\x96\\x87\\xE7\\xAB\\xA0\\xE3\\x81\\x8C\\xE5\\x85\\xA5\\xE3\\x82\\x8A\\xE3\\x81\\xBE\" \\\n    \"\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE8\\xAA\\xAD\\xE3\\x81\\xBF\\xE3\\x82\\x84\\xE3\\x81\\x99\\xE3\\x81\\x84\\xE3\" \\\n    \"\\x82\\x88\\xE3\\x81\\x86\\xE3\\x81\\xAB\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\\x82\" \\\n    \"\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x82\\x92\\xE4\\xBD\\xBF\\xE7\\x94\\xA8\\xE3\\x81\\x97\\xE3\\x81\\xA6\" \\\n    \"\\xE3\\x81\\x84\\xE3\\x81\\xBE\\xE3\\x81\\x99\\xE3\\x80\\x82\";\n\n\n#define lorem_ipsum_korean \\\n    \"\\xEC\\x9D\\xB4\\xEA\\xB2\\x83\\xEC\\x9D\\x80\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\\x85\\x8D\\xEC\\x8A\" \\\n    \"\\xA4\\xED\\x8A\\xB8\\xEC\\x9E\\x85\\xEB\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\\x20\\xEC\\x97\\xAC\\xEA\\xB8\\xB0\\xEC\" \\\n    \"\\x97\\x90\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEB\\x93\\x9C\\xEC\\x96\\xB4\\xEA\\xB0\\x80\" \\\n    \"\\xEB\\x8A\\x94\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEC\\x9E\\x88\\xEB\\x8B\\xA4\\x2E\\x20\" \\\n    \"\\xEC\\x9D\\xBD\\xEA\\xB8\\xB0\\x20\\xEC\\x89\\xBD\\xEA\\xB2\\x8C\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\" \\\n    \"\\x85\\x8D\\xEC\\x8A\\xA4\\xED\\x8A\\xB8\\xEB\\xA5\\xBC\\x20\\xEC\\x82\\xAC\\xEC\\x9A\\xA9\\xED\\x95\\xA9\\xEB\" \\\n    \"\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\";\n\n#define lorem_ipsum_emoji \\\n    \"\\xF0\\x9F\\x8D\\x95\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA6\\x84\\xF0\\x9F\\x92\\xBB\\xF0\\x9F\\x8E\\x89\\xF0\\x9F\" \\\n    \"\\x8C\\x88\\xF0\\x9F\\x90\\xB1\\xF0\\x9F\\x93\\x9A\\xF0\\x9F\\x8E\\xA8\\xF0\\x9F\\x8D\\x94\\xF0\\x9F\\x8D\\xA6\" \\\n    \"\\xF0\\x9F\\x8E\\xB8\\xF0\\x9F\\xA7\\xA9\\xF0\\x9F\\x8D\\xBF\\xF0\\x9F\\x93\\xB7\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\" \\\n    \"\\x91\\xBE\\xF0\\x9F\\x8C\\xAE\\xF0\\x9F\\x8E\\x88\\xF0\\x9F\\x9A\\xB2\\xF0\\x9F\\x8D\\xA9\\xF0\\x9F\\x8E\\xAE\" \\\n    \"\\xF0\\x9F\\x8D\\x89\\xF0\\x9F\\x8E\\xAC\\xF0\\x9F\\x90\\xB6\\xF0\\x9F\\x93\\xB1\\xF0\\x9F\\x8E\\xB9\\xF0\\x9F\" \\\n    \"\\xA6\\x96\\xF0\\x9F\\x8C\\x9F\\xF0\\x9F\\x8D\\xAD\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\\x8F\\x96\\xF0\\x9F\\xA6\\x8B\" \\\n    \"\\xF0\\x9F\\x8E\\xB2\\xF0\\x9F\\x8E\\xAF\\xF0\\x9F\\x8D\\xA3\\xF0\\x9F\\x9A\\x81\\xF0\\x9F\\x8E\\xAD\\xF0\\x9F\" \\\n    \"\\x91\\x9F\\xF0\\x9F\\x9A\\x82\\xF0\\x9F\\x8D\\xAA\\xF0\\x9F\\x8E\\xBB\\xF0\\x9F\\x9B\\xB8\\xF0\\x9F\\x8C\\xBD\" \\\n    \"\\xF0\\x9F\\x93\\x80\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA7\\x81\\xF0\\x9F\\x93\\xAF\\xF0\\x9F\\x8C\\xAF\\xF0\\x9F\" \\\n    \"\\x90\\xA5\\xF0\\x9F\\xA7\\x83\\xF0\\x9F\\x8D\\xBB\\xF0\\x9F\\x8E\\xAE\";\n\n\nstatic const char* content =\n    rt_glyph_lady_beetle \"\\n\"\n#if 1\n    \"Good bye Universe...\\n\"\n    \"Hello World!\\n\"\n    \"\\n\"\n    \"Ctrl+Shift and F5 starts/stops FUZZING test.\\n\"\n    \"\\n\"\n    \"FUZZING use rapid mouse clicks thus UI Fuzz button is hard to press use keyboard shortcut F5 to stop.\\n\"\n    \"\\n\"\n    \"0         10        20        30        40        50        60        70        80        90\\n\"\n    \"01234567890123456789012345678901234567890abcdefghi01234567890123456789012345678901234567890123456789\\n\"\n    \"0         10        20        30        40        50        60        70        80        90\\n\"\n    \"01234567890123456789012345678901234567890abcdefghi01234567890123456789012345678901234567890123456789\\n\"\n    \"\\n\"\n    \"0\" rt_glyph_chinese_jin4 rt_glyph_chinese_gong \"3456789\\n\"\n    \"\\n\"\n    rt_glyph_teddy_bear \"\\n\"\n    rt_glyph_teddy_bear rt_glyph_ice_cube rt_glyph_teddy_bear\n    rt_glyph_ice_cube rt_glyph_teddy_bear rt_glyph_ice_cube \"\\n\"\n    rt_glyph_teddy_bear rt_glyph_ice_cube rt_glyph_teddy_bear \" - \"\n    rt_glyph_ice_cube rt_glyph_teddy_bear rt_glyph_ice_cube \"\\n\"\n    \"\\n\"\n    lorem_ipsum_canonique \"\\n\"\n    lorem_ipsum_canonique;\n#endif\n\n// 566K angular2.min.js\n// https://get.cdnpkg.com/angular.js/2.0.0-beta.17/angular2.min.js\n// https://web.archive.org/web/20230104221014/https://get.cdnpkg.com/angular.js/2.0.0-beta.17/angular2.min.js\n\n// https://en.wikipedia.org/wiki/Lorem_ipsum\n\ntypedef struct {\n    char* text;\n    int32_t count; // at least 1KB\n    uint32_t seed; // seed for random generator\n    int32_t min_paragraphs; // at least 1\n    int32_t max_paragraphs;\n    int32_t min_sentences; // at least 1\n    int32_t max_sentences;\n    int32_t min_words; // at least 2\n    int32_t max_words;\n    const char* append; // append after each paragraph (e.g. extra \"\\n\")\n} ui_edit_lorem_ipsum_generator_params_t;\n\nstatic void ui_edit_lorem_ipsum_generator(ui_edit_lorem_ipsum_generator_params_t p) {\n    rt_fatal_if(p.count < 1024); // at least 1KB expected\n    rt_fatal_if_not(0 < p.min_paragraphs && p.min_paragraphs <= p.max_paragraphs);\n    rt_fatal_if_not(0 < p.min_sentences && p.min_sentences <= p.max_sentences);\n    rt_fatal_if_not(2 < p.min_words && p.min_words <= p.max_words);\n    char* s = p.text;\n    char* end = p.text + p.count - 64;\n    uint32_t paragraphs = p.min_paragraphs +\n        (p.min_paragraphs == p.max_paragraphs ? 0 :\n         rt_num.random32(&p.seed) % (p.max_paragraphs - p.min_paragraphs + 1));\n    while (paragraphs > 0 && s < end) {\n        uint32_t sentences_in_paragraph = p.min_sentences +\n            (p.min_sentences == p.max_sentences ? 0 :\n             rt_num.random32(&p.seed) % (p.max_sentences - p.min_sentences + 1));\n        while (sentences_in_paragraph > 0 && s < end) {\n            const uint32_t words_in_sentence = p.min_words +\n                (p.min_words == p.max_words ? 0 :\n                 rt_num.random32(&p.seed) % (p.max_words - p.min_words + 1));\n            for (uint32_t i = 0; i < words_in_sentence && s < end; i++) {\n                const int32_t ix = rt_num.random32(&p.seed) %\n                                   rt_countof(lorem_ipsum_words);\n                const char* word = lorem_ipsum_words[ix];\n                memcpy(s, word, strlen(word));\n                if (i == 0) { *s = (char)toupper(*s); }\n                s += strlen(word);\n                if (i < words_in_sentence - 1 && s < end) {\n                    const char* delimiter = \"\\x20\";\n                    int32_t punctuation = rt_num.random32(&p.seed) % 128;\n                    switch (punctuation) {\n                        case 0:\n                        case 1:\n                        case 2: delimiter = \", \"; break;\n                        case 3:\n                        case 4: delimiter = \"; \"; break;\n                        case 6: delimiter = \": \"; break;\n                        case 7: delimiter = \" - \"; break;\n                        default: break;\n                    }\n                    memcpy(s, delimiter, strlen(delimiter));\n                    s += strlen(delimiter);\n                }\n            }\n            if (sentences_in_paragraph > 1 && s < end) {\n                memcpy(s, \".\\x20\", 2);\n                s += 2;\n            } else {\n                *s++ = '.';\n            }\n            sentences_in_paragraph--;\n        }\n        if (paragraphs > 1 && s < end) {\n            *s++ = '\\n';\n        }\n        if (p.append != null && p.append[0] != 0) {\n            memcpy(s, p.append, strlen(p.append));\n            s += strlen(p.append);\n        }\n        paragraphs--;\n    }\n    *s = 0;\n//  rt_println(\"%s\\n\", p.text);\n}\n\nvoid ui_edit_init_with_lorem_ipsum(ui_edit_text_t* t) {\n    static char text[64 * 1024];\n    ui_edit_lorem_ipsum_generator_params_t p = {\n        .text = text,\n        .count = rt_countof(text),\n        .min_paragraphs = 4,\n        .max_paragraphs = 15,\n        .min_sentences  = 4,\n        .max_sentences  = 20,\n        .min_words      = 8,\n        .max_words      = 16,\n        .append         = \"\\n\"\n    };\n    #ifdef DEBUG\n        p.seed = 1; // repeatable sequence of pseudo random numbers\n    #else\n        p.seed = (int32_t)rt_clock.nanoseconds() | 0x1; // must be odd\n    #endif\n    ui_edit_lorem_ipsum_generator(p);\n    rt_swear(ui_edit_text.replace_utf8(t, null, content, -1, null));\n#if 1\n    ui_edit_range_t end = ui_edit_text.end_range(t);\n    rt_swear(ui_edit_text.replace_utf8(t, &end, \"\\n\\n\", -1, null));\n    end = ui_edit_text.end_range(t);\n    rt_swear(ui_edit_text.replace_utf8(t, &end, p.text, -1, null));\n    // test bad UTF8\n    static const char* th_bad_utf8 = \"\\xE0\\xB8\\x9A\\xE0\\xB8\\xA3\\xE0\\xB8\\xB4\\xE0\\xB9\\x80\\xE0\\xB8\\xA7\\xE0\\xB8\\x93\\xE0\\xB8\\x8A\\xE0\\xB8\\xB8\\xE0\\xB8\\x94\\xE0\\xB8\\xA5\\xE0\\xB8\\xB0\\xE0\\xB8\\xA5\\xE0\\xB8\\xB0\\xE0\\xB8\\xAA\\xE0\\xB8\\xB2\\xE0\\xB8\\x87\\xE0\\xE0\\xB9\\x80\\xE0\\xB8\\xA3\\xE0\\xB8\\x87\\xE0\\xB9\\x84\\xE0\\xB8\\xA5\\xE0\\xB8\\xA1\\xE0\\xB8\\x95\\xE0\\xB8\\xA3\\xE0\\xB8\\xB4\\xE0\\xB8\\xA1\\xE0\\xB8\\xAD\\xE0\\xB9\\x88\\xE0\\xB8\\xB2\\xE0\\xB8\\x94\\xE0\\xB8\\xAD\\xE0\\xB8\\x94\\xE0\\xB8\\xAA\\xE0\\xB9\\x80\\xE0\\xB8\\xA3\\xE0\\xB8\\xA2\\xE0\\xB8\\xA2\\xE0\\xB8\\xA7\\xE0\\xB8\\xA5\\xE0\\xB8\\xB1\\xE0\\xB8\\x9A\\xE0\\xB8\\x81\\xE0\\xB8\\xB4\\xE0\\xB8\\x9B\\xE0\\xB8\\x81\\xE0\\xB8\\xA7\\xE0\\xB8\\xB1\\xE0\\xB8\\x87\\x2E\";\n    end = ui_edit_text.end_range(t);\n//  rt_println(\"%s\", th_bad_utf8);\n    bool expected_false = ui_edit_text.replace_utf8(t, &end, th_bad_utf8, -1, null);\n    rt_swear(expected_false == false);\n    static const char* en_sentence_utf8 = \"\\x54\\x68\\x65\\x20\\x71\\x75\\x69\\x63\\x6b\\x20\\x62\\x72\\x6f\\x77\\x6e\\x20\\x66\\x6f\\x78\\x20\\x6a\\x75\\x6d\\x70\\x73\\x20\\x6f\\x76\\x65\\x72\\x20\\x74\\x68\\x65\\x20\\x6c\\x61\\x7a\\x79\\x20\\x64\\x6f\\x67\\x2e\";\n    static const char* es_sentence_utf8 = \"\\x45\\x6c\\x20\\x76\\x65\\x6c\\x6f\\x7a\\x20\\x6d\\x75\\x72\\x63\\x69\\x65\\xcc\\x81\\x6c\\x61\\x67\\x6f\\x20\\x68\\x69\\x6e\\x64\\x75\\xcc\\x81\\x20\\x63\\x6f\\x6d\\x69\\xcc\\x81\\x61\\x20\\x66\\x65\\x6c\\x69\\x7a\\x20\\x63\\x61\\x72\\x64\\x69\\x6c\\x6c\\x6f\\x20\\x79\\x20\\x6b\\x69\\x77\\x69\\x2e\";\n    static const char* fr_sentence_utf8 = \"\\x50\\x6f\\x72\\x74\\x65\\x7a\\x20\\x63\\x65\\x20\\x76\\x69\\x65\\x75\\x78\\x20\\x77\\x68\\x69\\x73\\x6b\\x79\\x20\\x61\\x75\\x20\\x6a\\x75\\x67\\x65\\x20\\x62\\x6c\\x6f\\x6e\\x64\\x20\\x71\\x75\\x69\\x20\\x66\\x75\\x6d\\x65\\x2e\";\n    static const char* de_sentence_utf8 = \"\\x56\\x69\\x63\\x74\\x6f\\x72\\x20\\x6a\\x61\\x67\\x74\\x20\\x7a\\x77\\xc3\\xb6\\x6c\\x66\\x20\\x42\\x6f\\x78\\x6b\\xc3\\xa4\\x6d\\x70\\x66\\x65\\x72\\x20\\x71\\x75\\x65\\x72\\x20\\xc3\\xbc\\x62\\x65\\x72\\x20\\x64\\x65\\x6e\\x20\\x67\\x72\\x6f\\xc3\\x9f\\x65\\x6e\\x20\\x53\\x79\\x6c\\x74\\x65\\x72\\x20\\x44\\x65\\x69\\x63\\x68\\x2e\";\n    static const char* pl_sentence_utf8 = \"\\x50\\x63\\x68\\x6e\\xc4\\x85\\xc4\\x87\\x20\\x77\\x20\\x74\\xc4\\x99\\x20\\xc5\\x82\\xc3\\xb3\\xc4\\x87\\x20\\x6a\\x65\\xc5\\xbc\\x61\\x20\\x6c\\x75\\x62\\x20\\x6f\\xc5\\x9b\\x6d\\x20\\x73\\x6b\\x72\\x7a\\x79\\xc5\\x84\\x20\\x66\\x69\\x67\\x2e\";\n    static const char* cs_sentence_utf8 = \"\\x50\\xc5\\x99\\xc3\\xad\\x6c\\xc3\\xad\\xc5\\xa1\\x20\\xc5\\xbe\\x6c\\x75\\xc5\\xa5\\x6f\\x75\\xc4\\x8d\\x6b\\x79\\x20\\x6b\\xc5\\xaf\\xc5\\x88\\x20\\xc3\\xba\\x70\\xc4\\x9bl\\x20\\xc4\\x8f\\xc3\\xa1\\x62\\x65\\x6c\\x73\\x6b\\xc3\\xa9\\x20\\xc3\\xb3\\x64\\x79\\x2e\";\n    static const char* hu_sentence_utf8 = \"\\xc3\\x81\\x72\\x76\\xc3\\xad\\x7a\\x74\\xc5\\xb1\\x72\\xc5\\x91\\x20\\x74\\xc3\\xbc\\x6b\\xc3\\xb6\\x72\\x66\\xc3\\xba\\x72\\xc3\\xb3\\x67\\xc3\\xa9\\x70\\x2e\";\n    static const char* ru_sentence_utf8 = \"\\xd0\\xa1\\xd1\\x8a\\xd0\\xb5\\xd1\\x88\\xd1\\x8c\\x20\\xd0\\xb6\\xd0\\xb5\\x20\\xd0\\xb5\\xd1\\x89\\xd1\\x91\\x20\\xd1\\x8d\\xd1\\x82\\xd0\\xb8\\xd1\\x85\\x20\\xd0\\xbc\\xd1\\x8f\\xd0\\xb3\\xd0\\xba\\xd0\\xb8\\xd1\\x85\\x20\\xd1\\x84\\xd1\\x80\\xd0\\xb0\\xd0\\xbd\\xd1\\x86\\xd1\\x83\\xd0\\xb7\\xd1\\x81\\xd0\\xba\\xd0\\xb8\\xd1\\x85\\x20\\xd0\\xb1\\xd1\\x83\\xd0\\xbb\\xd0\\xbe\\xd0\\xba\\x20\\xd0\\xb4\\xd0\\xb0\\x20\\xd0\\xb2\\xd1\\x8b\\xd0\\xbf\\xd0\\xb5\\xd0\\xb9\\x20\\xd1\\x87\\xd0\\xb0\\xd1\\x8e\\x2e\";\n    static const char* pt_sentence_utf8 = \"\\x4f\\x20\\x76\\x65\\x6c\\x6f\\x7a\\x20\\x63\\x61\\x63\\x68\\x6f\\x72\\x72\\x6f\\x20\\x6d\\x61\\x72\\x72\\x6f\\x6d\\x20\\x7a\\x61\\x70\\x61\\x20\\x72\\x61\\x70\\x69\\x64\\x61\\x6d\\x65\\x6e\\x74\\x65\\x20\\x75\\x6d\\x20\\x6b\\x69\\x77\\x69\\x2e\";\n    static const char* it_sentence_utf8 = \"\\x51\\x75\\x65\\x6c\\x20\\x66\\x6f\\x78\\x20\\x62\\x72\\x75\\x6e\\x6f\\x20\\x73\\x61\\x6c\\x74\\x61\\x20\\x69\\x6c\\x20\\x63\\x61\\x6e\\x65\\x20\\x70\\x69\\x67\\x72\\x6f\\x20\\x6c\\x61\\x7a\\x7a\\x6f\\x2e\";\n    static const char* nl_sentence_utf8 = \"\\x44\\x65\\x20\\x76\\x6c\\x75\\x67\\x20\\x6a\\x65\\x20\\x7a\\x65\\x76\\x65\\x6e\\x20\\x71\\x75\\x69\\x73\\x74\\x20\\x64\\x65\\x20\\x73\\x63\\x68\\x61\\x74\\x20\\x62\\x72\\x75\\x69\\x6e\\x20\\x6c\\x61\\x7a\\x65\\x72\\x2e\";\n    static const char* el_sentence_utf8 = \"\\xce\\x9e\\xce\\xb5\\xcf\\x83\\xce\\xba\\xce\\xb5\\xcf\\x80\\xce\\xac\\xce\\xb6\\xcf\\x89\\x20\\xcf\\x84\\xce\\xb7\\xce\\xbd\\x20\\xcf\\x88\\xcf\\x85\\xcf\\x87\\xce\\xbf\\xcf\\x86\\xce\\xb8\\xcf\\x8c\\xcf\\x81\\xce\\xb1\\x20\\xce\\xb2\\xce\\xb4\\xce\\xb5\\xce\\xbb\\xcf\\x85\\xce\\xb3\\xce\\xbc\\xce\\xaf\\xce\\xb1\\x2e\";\n    static const char* tr_sentence_utf8 = \"\\xc3\\x87\\xc4\\xb1\\xc4\\x9f\\xc4\\xb1\\x6c\\xc3\\xb6\\xc4\\x9f\\xc4\\xb1\\x6e\\x6d\\xc3\\xa7\\xc3\\xa7\\xc3\\xa7\";\n    static const char* uk_sentence_utf8 = \"\\xd0\\xa4\\xd0\\xb0\\xd0\\xbd\\xd0\\xba\\xd1\\x83\\xd0\\xb2\\xd0\\xb0\\xd0\\xbb\\xd0\\xb0\\x20\\xd0\\xbd\\xd0\\xb0\\x20\\xd0\\xb1\\xd0\\xb5\\xd1\\x80\\xd0\\xb5\\xd0\\xb7\\xd1\\x96\\x20\\xd0\\xb2\\xd0\\xb5\\xd0\\xbb\\xd0\\xb8\\xd0\\xba\\xd1\\x83\\xd1\\x8e\\x20\\xd0\\xbf\\xd0\\xbe\\xd1\\x80\\xd1\\x86\\xd1\\x96\\xd1\\x8e\\x20\\xd0\\xbc\\xd0\\xb0\\xd0\\xbb\\xd0\\xb8\\xd0\\xbd\\xd0\\xbe\\xd0\\xb2\\xd0\\xbe\\xd0\\xb3\\xd0\\xbe\\x20\\xd0\\xb2\\xd0\\xb0\\xd1\\x80\\xd0\\xb5\\xd0\\xbd\\xd0\\xbd\\xd1\\x8f\\x2e\";\n    static const char* bg_sentence_utf8 = \"\\xd0\\x9b\\xd1\\x8e\\xd0\\xb1\\xd1\\x8f\\x20\\xd1\\x81\\xd0\\xb2\\xd0\\xbe\\xd0\\xb9\\x20\\xd0\\xbc\\xd0\\xb5\\xd0\\xbb\\xd1\\x8a\\xd0\\xba\\x20\\xd1\\x86\\xd0\\xb2\\xd1\\x8f\\xd1\\x82\\x2c\\x20\\xd0\\xb6\\xd0\\xb0\\xd0\\xba\\x20\\xd0\\xb4\\xd1\\x8a\\xd0\\xb2\\xd1\\x87\\xd0\\xb5\\x20\\xd1\\x88\\xd1\\x83\\xd0\\xbc\\xd0\\xb5\\xd0\\xbd\\x20\\xd1\\x85\\xd0\\xb2\\xd1\\x8a\\xd1\\x80\\xd1\\x87\\xd0\\xb0\\xd1\\x89\\x20\\xd0\\xbf\\xd1\\x83\\xd0\\xb4\\xd0\\xb5\\xd0\\xbb\\x2e\";\n    static const char* sr_sentence_utf8 = \"\\xd0\\x8f\\xd0\\xb5\\xd0\\xbf\\x20\\xd1\\x98\\xd0\\xb5\\x20\\xd1\\x88\\xd1\\x83\\xd0\\xbf\\xd0\\xb0\\xd1\\x99\\x2c\\x20\\xd1\\x84\\xd0\\xbe\\xd0\\xbb\\xd0\\xb8\\xd1\\x80\\xd0\\xb0\\xd0\\xbd\\xd1\\x82\\xd1\\x81\\xd0\\xba\\xd0\\xb0\\x20\\xd0\\xb6\\xd0\\xb5\\xd0\\xbd\\xd0\\xb0\\x20\\xd1\\x83\\xd0\\xb2\\xd0\\xb5\\xd0\\xba\\x20\\xd0\\xbc\\xd0\\xb0\\xd1\\x81\\xd0\\xba\\xd0\\xb8\\xd1\\x80\\xd0\\xb0\\x20\\xd1\\x99\\xd1\\x83\\xd0\\xbf\\xd0\\xba\\xd0\\xb5\\x20\\xd0\\xb4\\xd0\\xb5\\xd1\\x87\\xd0\\xba\\xd0\\xb5\\x2e\";\n    static const char* sq_sentence_utf8 = \"\\x5A\\x68\\x76\\x69\\x6C\\x6F\\x6A\\x61\\x20\\xC3\\x87\\x64\\x6F\\x6B\\x6F\\x72\\x20\\x70\\xC3\\xAB\\x72\\x62\\x65\\x6C\\x69\\x6E\\xC3\\xAB\\x20\\x6E\\xC3\\xAB\\x6E\\x20\\x64\\x69\\x6B\\x75\\x72\\x20\\x6E\\x6F\\x70\\x61\\x6C\\x20\\x74\\x65\\x20\\x66\\x6A\\x61\\x6C\\xC3\\xAB\\x2E\";\n    static const char* sl_sentence_utf8 = \"\\x42\\x6C\\x61\\x67\\x6F\\x76\\x6F\\x6C\\x6A\\x65\\x6E\\x20\\xC5\\xBE\\x69\\x6E\\x6A\\x61\\x6A\\x20\\x70\\x72\\x61\\x7A\\x6E\\x69\\x20\\xC5\\xA1\\x63\\x72\\x61\\x6C\\x20\\x70\\x6F\\x64\\x20\\x76\\x69\\x73\\x6F\\x6B\\x20\\xC5\\xBE\\x65\\x6C\\x76\\x65\\x6C\\x20\\xC4\\x8D\\x75\\x64\\x6F\\x76\\x2E\";\n    static const char* lt_sentence_utf8 = \"\\xC4\\x84\\xC5\\xBE\\x75\\x73\\x69\\x6E\\xC4\\x97\\x20\\xC5\\xA1\\x75\\x6E\\x79\\x73\\x74\\x61\\x20\\x70\\x6F\\x20\\x75\\xC5\\xB3\\x73\\x69\\x75\\x73\\x20\\xC5\\xBE\\x61\\x6C\\x6D\\x61\\x20\\xC5\\xBE\\x69\\x65\\x6D\\x6F\\x6E\\x69\\x73\\x20\\x6D\\x61\\x6C\\x65\\x2E\";\n    static const char* lv_sentence_utf8 = \"\\xC4\\x80\\x72\\x74\\x61\\x75\\x72\\x20\\x6E\\x65\\x73\\x65\\x65\\x64\\x7A\\xC4\\xAB\\x76\\x73\\x20\\x6A\\x61\\x75\\x20\\xC5\\xA1\\x6B\\x6F\\x6C\\x75\\x20\\xC5\\xA1\\x63\\x65\\x6E\\xC5\\xA1\\x20\\xC4\\x8D\\x69\\x67\\x61\\x6E\\x20\\xC4\\xBC\\xC4\\x81\\x70\\xC4\\x81\\x20\\xC5\\xA1\\x6B\\x61\\x72\\x62\\x69\\x6E\\x73\\x2E\";\n    static const char* et_sentence_utf8 = \"\\xC3\\x9C\\x68\\x65\\x74\\x6F\\x72\\x75\\x6E\\x65\\x20\\x74\\x6F\\x68\\x74\\x75\\x6D\\x20\\x6B\\x61\\x73\\x6B\\x75\\x73\\x20\\xC3\\xB5\\x70\\x65\\x64\\x20\\x6B\\xC3\\xB5\\x72\\x67\\x75\\x6D\\x20\\xC3\\xBC\\x72\\x69\\x74\\x75\\x73\\x20\\xC3\\xB5\\x75\\x20\\x6A\\xC3\\xA4\\xC3\\xA4\\x6E\\x2E\";\n    static const char* ga_sentence_utf8 = \"\\x42\\x68\\x69\\x20\\x66\\xC3\\xA1\\x6C\\x74\\x61\\x20\\x6D\\x68\\xC3\\xA1\\x69\\x72\\xC3\\xAD\\x2C\\x20\\x63\\x68\\x75\\x69\\x20\\x66\\xC3\\xA9\\x6E\\x65\\x20\\x6D\\x68\\xC3\\xA9\\x69\\x72\\x20\\xC3\\xA1\\x20\\x63\\x68\\x6F\\x69\\x73\\x20\\xC3\\xA9\\x69\\x6E\\x2E\";\n    static const char* cy_sentence_utf8 = \"\\x59\\x20\\x66\\x66\\x6F\\x72\\x6B\\x20\\x67\\x72\\x65\\x6E\\x66\\x6F\\x72\\x20\\x20\\x62\\x6C\\x61\\x77\\x64\\x64\\x20\\x6C\\x6C\\x65\\x20\\x77\\x61\\x69\\x74\\x68\\xC3\\xAF\\x73\\x20\\xC3\\xA2\\x73\\x2E\";\n    static const char* is_sentence_utf8 = \"\\xC3\\x86\\x72\\x69\\x20\\x6C\\x65\\x74\\x6A\\x61\\x72\\xC3\\xAD\\x20\\x6E\\xC3\\xBD\\x20\\xC3\\xB3\\x76\\x61\\x72\\x2E\\x20\\xC3\\x9E\\xC3\\xA9\\xC3\\xB0\\x20\\x65\\x72\\x20\\x61\\xC3\\xB0\\x20\\x76\\x69\\x6C\\x6A\\x61\\x72\\xC3\\xAD\\x20\\x6D\\xC3\\xAD\\x6E\\x2E\";\n    static const char* mt_sentence_utf8 = \"\\xC4\\xA0\\x75\\x73\\x20\\x6A\\x6F\\x62\\x62\\x61\\x20\\x6C\\x69\\x20\\x6C\\x61\\x2E\";\n    static const char* he_sentence_utf8 = \"\\xD7\\x90\\xD7\\x91\\xD7\\x92\\xD7\\x93\\xD7\\x94\\xD7\\x95\\xD7\\x96\\xD7\\x97\\xD7\\x98\\xD7\\x99\\xD7\\x9A\\xD7\\x9B\\xD7\\x9C\\xD7\\x9D\\xD7\\x9E\\xD7\\x9F\\xD7\\xA1\\xD7\\xA2\\xD7\\xA4\\xD7\\xA6\\xD7\\xA7\\xD7\\xA8\\xD7\\xA9\\xD7\\xAA\";\n    static const char* ar_sentence_utf8 = \"\\xD8\\xB5\\xD9\\x90\\xD9\\x81\\xD9\\x92\\x20\\xD8\\xAE\\xD9\\x90\\xD9\\x84\\xD9\\x92\\xD8\\xB9\\xD9\\x8E\\xD8\\xAA\\xD9\\x8E\\xD9\\x83\\xD9\\x8E\\x20\\xD8\\xA7\\xD9\\x84\\xD9\\x8A\\xD9\\x8E\\xD9\\x88\\xD9\\x92\\xD9\\x85\\xD9\\x8E\\x20\\xD8\\xBA\\xD9\\x8E\\xD8\\xB7\\xD9\\x91\\xD9\\x8E\\xD8\\xB3\\xD9\\x8E\\x20\\xD8\\xAB\\xD9\\x8E\\xD9\\x88\\xD8\\xA8\\xD9\\x8E\\xD9\\x83\\xD9\\x8E\\x20\\xD9\\x81\\xD9\\x90\\xD9\\x8A\\xD9\\x92\\x20\\xD8\\xAD\\xD9\\x8E\\xD9\\x84\\xD9\\x8A\\xD8\\xA8\\xD9\\x8D\\x20\\xD8\\xAC\\xD9\\x8E\\xD8\\xA7\\xD9\\x85\\xD9\\x90\\xD8\\xAF\\xD9\\x90\\x2E\";\n    static const char* hi_sentence_utf8 = \"\\xE0\\xA4\\x97\\xE0\\xA4\\xA3\\xE0\\xA5\\x87\\xE0\\xA4\\xB6\\x20\\xE0\\xA4\\xAA\\xE0\\xA5\\x82\\xE0\\xA4\\x9C\\xE0\\xA4\\xA8\\x20\\xE0\\xA4\\x95\\xE0\\xA4\\xB0\\xE0\\xA5\\x8B\\x20\\xE0\\xA4\\x94\\xE0\\xA4\\xB0\\x20\\xE0\\xA4\\xAA\\xE0\\xA5\\x8D\\xE0\\xA4\\xB0\\xE0\\xA4\\xB8\\xE0\\xA4\\xBE\\xE0\\xA4\\xA6\\x20\\xE0\\xA4\\x9A\\xE0\\xA4\\xA2\\xE0\\xA4\\xBC\\xE0\\xA4\\xBE\\xE0\\xA4\\x93\\xE0\\xA5\\xA4\";\n    static const char* bn_sentence_utf8 = \"\\xE0\\xA6\\x8F\\xE0\\xA6\\x95\\xE0\\xA6\\xAF\\xE0\\xA7\\x81\\xE0\\xA6\\x97\\xE0\\xA7\\x87\\x20\\xE0\\xA6\\xAD\\xE0\\xA6\\xBE\\xE0\\xA6\\xB0\\xE0\\xA6\\xA4\\xE0\\xA6\\xAC\\xE0\\xA6\\xB0\\xE0\\xA7\\x8D\\xE0\\xA6\\xB7\\xE0\\xA7\\x87\\xE0\\xA6\\xB0\\x20\\xE0\\xA6\\xAE\\xE0\\xA6\\xBE\\xE0\\xA6\\xA8\\xE0\\xA7\\x81\\xE0\\xA6\\xB7\\x20\\xE0\\xA6\\xB8\\xE0\\xA6\\xBE\\xE0\\xA6\\xB0\\xE0\\xA6\\xBE\\x20\\xE0\\xA6\\x9C\\xE0\\xA6\\x97\\xE0\\xA6\\xA4\\xE0\\xA7\\x87\\x20\\xE0\\xA6\\xB6\\xE0\\xA7\\x8D\\xE0\\xA6\\xB0\\xE0\\xA7\\x87\\xE0\\xA6\\xB7\\xE0\\xA7\\x8D\\xE0\\xA6\\xA0\\x20\\xE0\\xA6\\xB8\\xE0\\xA7\\x8D\\xE0\\xA6\\xA5\\xE0\\xA6\\xBE\\xE0\\xA6\\xA8\\x20\\xE0\\xA6\\x85\\xE0\\xA6\\xA7\\xE0\\xA6\\xBF\\xE0\\xA6\\x95\\xE0\\xA6\\xBE\\xE0\\xA6\\xB0\\x20\\xE0\\xA6\\x95\\xE0\\xA6\\xB0\\xE0\\xA6\\xBF\\xE0\\xA6\\xAC\\xE0\\xA7\\x87\\xE0\\xA5\\xA4\";\n    static const char* ta_sentence_utf8 = \"\\xe0\\xae\\x9a\\xe0\\xae\\xbf\\xe0\\xae\\xb5\\xe0\\xae\\xaa\\xe0\\xaf\\x8d\\xe0\\xae\\xaa\\xe0\\xaf\\x81\\x20\\xe0\\xae\\xa8\\xe0\\xae\\xb0\\xe0\\xae\\xbf\\x20\\xe0\\xae\\xae\\xe0\\xae\\xa8\\xe0\\xaf\\x8d\\xe0\\xae\\xa4\\x20\\xe0\\xae\\xa8\\xe0\\xae\\xbe\\xe0\\xae\\xaf\\xe0\\xaf\\x88\\x20\\xe0\\xae\\xa4\\xe0\\xae\\xbe\\xe0\\xae\\xa3\\xe0\\xaf\\x8d\\xe0\\xae\\x9f\\xe0\\xae\\xbf\\xe0\\xae\\xaf\\xe0\\xae\\xa4\\xe0\\xaf\\x81\\x2e\";\n    static const char* te_sentence_utf8 = \"\\xE0\\xB0\\x8E\\xE0\\xB0\\x97\\xE0\\xB0\\xB8\\xE0\\xB0\\xBF\\xE0\\xB0\\xA8\\x20\\xE0\\xB0\\x95\\xE0\\xB0\\x82\\xE0\\xB0\\xA6\\xE0\\xB0\\xAE\\xE0\\xB1\\x81\\xE0\\xB0\\xB2\\xE0\\xB1\\x81\\x20\\xE0\\xB0\\xAF\\xE0\\xB0\\xB5\\xE0\\xB1\\x8D\\xE0\\xB0\\xB5\\xE0\\xB0\\xA8\\xE0\\xB0\\xBE\\xE0\\xB0\\xA8\\xE0\\xB1\\x8D\\xE0\\xB0\\xA8\\xE0\\xB0\\xBF\\x20\\xE0\\xB0\\x9A\\xE0\\xB1\\x86\\xE0\\xB0\\xB0\\xE0\\xB0\\xBF\\xE0\\xB0\\xAA\\xE0\\xB1\\x87\\x20\\xE0\\xB0\\x9A\\xE0\\xB0\\x82\\xE0\\xB0\\xA6\\xE0\\xB0\\xBE\\xE0\\xB0\\xB2\\x20\\xE0\\xB0\\xA6\\xE0\\xB0\\xBF\\xE0\\xB0\\xA8\\xE0\\xB0\\x95\\xE0\\xB0\\xB0\\xE0\\xB0\\x82\\x2E\";\n    static const char* kn_sentence_utf8 = \"\\xE0\\xB2\\x85\\xE0\\xB2\\xA8\\xE0\\xB2\\x95\\xE0\\xB3\\x8D\\xE0\\xB2\\x95\\xE0\\xB2\\xA8\\x20\\xE0\\xB2\\x97\\xE0\\xB2\\xB4\\xE0\\xB3\\x81\\xE0\\xB2\\xB8\\xE0\\xB2\\xBF\\x20\\xE0\\xB2\\x8E\\xE0\\xB2\\xA8\\xE0\\xB3\\x8D\\xE0\\xB2\\xA8\\xE0\\xB2\\xA6\\xE0\\xB2\\xBF\\xE0\\xB2\\xA8\\xE0\\xB2\\x95\\xE0\\xB2\\x82\\x20\\xE0\\xB2\\xA6\\xE0\\xB2\\xBF\\xE0\\xB2\\x82\\xE0\\xB2\\xA1\\xE0\\xB3\\x81\\xE0\\xB2\\x95\\xE0\\xB3\\x86\\xE0\\xB2\\xA6\\x20\\xE0\\xB2\\x9A\\xE0\\xB2\\x82\\xE0\\xB2\\xA6\\xE0\\xB2\\x95\\xE0\\xB3\\x86\\xE0\\xB2\\x9F\\xE0\\xB2\\xA1\\xE0\\xB3\\x81\\xE0\\xB2\\xB5\\xE0\\xB2\\xBE\\xE0\\xB2\\xB0\\xE0\\xB2\\x93\\x2E\";\n    static const char* ml_sentence_utf8 = \"\\xE0\\xB4\\x8F\\xE0\\xB4\\x95\\xE0\\xB4\\xB4\\xE0\\xB4\\x82\\x20\\xE0\\xB4\\xA4\\xE0\\xB4\\xB0\\xE0\\xB4\\x82\\x20\\xE0\\xB4\\xAE\\xE0\\xB4\\xA3\\xE0\\xB4\\xB1\\xE0\\xB4\\x96\\xE0\\xB4\\xB7\\xE0\\xB4\\x82\\x20\\xE0\\xB4\\x89\\xE0\\xB4\\xA3\\xE0\\xB5\\x8D\\xE0\\xB4\\xAE\\xE0\\xB5\\x82\\x20\\xE0\\xB4\\xA8\\xE0\\xB4\\xA4\\xE0\\xB4\\x95\\xE0\\xB5\\x8D\\xE0\\xB4\\x95\\xE0\\xB5\\x8B\\x20\\xE0\\xB4\\x87\\xE0\\xB4\\xA4\\xE0\\xB4\\xBF\\xE0\\xB4\\xB0\\xE0\\xB5\\x81\\xE0\\xB4\\xA8\\xE0\\xB5\\x8D\\xE0\\xB4\\xA8\\xE0\\xB5\\x8B\\x20\\xE0\\xB4\\xA4\\xE0\\xB4\\xA3\\xE0\\xB5\\x8D\\xE0\\xB4\\xA8\\xE0\\xB5\\x81\\x2E\";\n    static const char* si_sentence_utf8 = \"\\xE0\\xB6\\xB8\\xE0\\xB7\\x9A\\x20\\xE0\\xB6\\xB6\\xE0\\xB7\\x8F\\xE0\\xB6\\xB1\\xE0\\xB6\\xA7\\xE0\\xB7\\x8A\\xE0\\xB6\\xA7\\x20\\xE0\\xB6\\xB8\\xE0\\xB6\\x9A\\xE0\\xB7\\x8A\\xE0\\xB6\\xA7\\xE0\\xB6\\xA1\\x20\\xE0\\xB6\\xA2\\xE0\\xB6\\xA7\\xE0\\xB6\\xA2\\xE0\\xB7\\x92\\x20\\xE0\\xB6\\xAD\\xE0\\xB6\\xB1\\xE0\\xB7\\x8A\\x20\\xE0\\xB6\\xBA\\xE0\\xB7\\x8F\\xE0\\xB6\\xA9\\x20\\xE0\\xB6\\xA7\\xE0\\xB6\\xB1\\x20\\xE0\\xB7\\x84\\xE0\\xB7\\x90\\xE0\\xB6\\xB8\\xE0\\xB7\\x9A\\x2E\";\n    static const char* th_sentence_utf8 = \"\\xe0\\xb8\\x9a\\xe0\\xb8\\xa3\\xe0\\xb8\\xb4\\xe0\\xb9\\x80\\xe0\\xb8\\xa7\\xe0\\xb8\\x93\\xe0\\xb8\\xaa\\xe0\\xb8\\xb8\\xe0\\xb8\\x94\\xe0\\xb8\\xa5\\xe0\\xb8\\xb0\\xe0\\xb8\\xa5\\xe0\\xb8\\xb0\\xe0\\xb8\\xaa\\xe0\\xb8\\xb2\\xe0\\xb8\\x87\\xe0\\xb8\\x82\\xe0\\xb8\\xad\\xe0\\xb8\\x87\\xe0\\xb9\\x84\\xe0\\xb8\\xa5\\xe0\\xb8\\xa1\\xe0\\xb9\\x8c\\xe0\\xb8\\x95\\xe0\\xb8\\xa3\\xe0\\xb8\\xb4\\xe0\\xb8\\xa1\\xe0\\xb8\\xad\\xe0\\xb9\\x88\\xe0\\xb8\\xb2\\xe0\\xb8\\x94\\xe0\\xb8\\xad\\xe0\\xb8\\x94\\xe0\\xb9\\x80\\xe0\\xb8\\xaa\\xe0\\xb8\\xa3\\xe0\\xb8\\xa2\\xe0\\xb8\\xa7\\xe0\\xb8\\xa5\\xe0\\xb8\\xb1\\xe0\\xb8\\x9a\\xe0\\xb8\\x81\\xe0\\xb8\\xb4\\xe0\\xb8\\x9b\\xe0\\xb8\\x81\\xe0\\xb8\\xa7\\xe0\\xb8\\xb1\\xe0\\xb8\\x87\\x2e\";\n    static const char* lo_sentence_utf8 = \"\\xE0\\xB8\\x9A\\xE0\\xB8\\xB3\\xE0\\xB8\\xA7\\xE0\\xB8\\x94\\xE0\\xB8\\xA5\\xE0\\xB8\\xB2\\xE0\\xB8\\xA1\\xE0\\xB8\\xAA\\xE0\\xB8\\xB2\\xE0\\xB8\\x87\\xE0\\xB8\\x82\\xE0\\xB9\\x80\\xE0\\xB8\\xA7\\xE0\\xB8\\x87\\xE0\\xB8\\x9D\\xE0\\xB8\\xB2\\xE0\\xB9\\x80\\xE0\\xB8\\xA1\\xE0\\xB8\\xAA\\xE0\\xB9\\x83\\xE0\\xB8\\xB8\\xE0\\xB8\\x88\\xE0\\xB8\\xA7\\xE0\\xB8\\xB2\\xE0\\xB8\\x99\\xE0\\xB9\\x88\\xE0\\xB8\\xB2\\xE0\\xB8\\x8A\\xE0\\xB9\\x8C\\xE0\\xB9\\x89\\xE0\\xB8\\x8A\\xE0\\xB9\\x89\\xE0\\xB8\\xA7\\xE0\\xB8\\xA2\\xE0\\xB8\\xAA\\xE0\\xB8\\xA7\\xE0\\xB8\\xAA\\xE0\\xB8\\xA2\\xE0\\xB8\\xB2\\xE0\\xB8\\x88\\xE0\\xB8\\x9D\\xE0\\xB8\\x81\\xE0\\xB8\\x95\\xE0\\xB8\\xAD\\xE0\\xB9\\x80\\xE0\\xB8\\xB1\\xE0\\xB9\\x88\\xE0\\xB8\\xA7\\xE0\\xB8\\x81\\xE0\\xB8\\xA3\\xE0\\xB8\\xB2\\xE0\\xB9\\x81\\xE0\\xB8\\x94\\xE0\\xB8\\x87\\xE0\\xB8\\xAD\\xE0\\xB8\\x81\\xE0\\xB8\\xB8\\xE0\\xB9\\x80\\xE0\\xB8\\xA1\\xE0\\xB9\\x87\\xE0\\xB8\\xA2\\xE0\\xB9\\x84\\xE0\\xB8\\xA1\\xE0\\xB8\\x97\\xE0\\xB8\\xAD\\xE0\\xB9\\x88\\xE0\\xB8\\x87\\xE0\\xB8\\xA2\\xE0\\xB8\\xAD\\xE0\\xB9\\x88\\xE0\\xB8\\xA7\\xE0\\xB8\\xAA\\xE0\\xB8\\xA1\\xE0\\xB8\\xAA\\xE0\\xB8\\xA3\\xE0\\xB9\\x80\\xE0\\xB8\\xA2\\xE0\\xB9\\x88\\xE0\\xB8\\xA3\\xE0\\xB8\\xB8\\xE0\\xB8\\xB8\\xE0\\xB8\\xB4\\xE0\\xB9\\x83\\xE0\\xB8\\x8A\\xE0\\xB9\\x88\\xE0\\xB8\\x88\\xE0\\xB8\\x87\\xE0\\xB8\\x9B\\xE0\\xB8\\x9A\\xE0\\xB8\\x82\\xE0\\xB8\\xB9\\xE0\\xB8\\xB9\\xE0\\xB8\\x8D\\x2E\";\n    static const char* ka_sentence_utf8 = \"\\xe1\\x83\\x9b\\xe1\\x83\\x90\\xe1\\x83\\x92\\xe1\\x83\\x90\\xe1\\x83\\xa0\\xe1\\x83\\x98\\x20\\xe1\\x83\\xa7\\xe1\\x83\\x90\\xe1\\x83\\x95\\xe1\\x83\\x98\\xe1\\x83\\xa1\\xe1\\x83\\xa4\\xe1\\x83\\x94\\xe1\\x83\\xa0\\xe1\\x83\\x98\\x20\\xe1\\x83\\x9b\\xe1\\x83\\x94\\xe1\\x83\\x9a\\xe1\\x83\\x98\\xe1\\x83\\x90\\x20\\xe1\\x83\\xae\\xe1\\x83\\xa2\\xe1\\x83\\x94\\xe1\\x83\\x91\\xe1\\x83\\x90\\x20\\xe1\\x83\\x96\\xe1\\x83\\x90\\xe1\\x83\\xa0\\xe1\\x83\\x9b\\xe1\\x83\\x90\\xe1\\x83\\xaa\\xe1\\x83\\x98\\x20\\xe1\\x83\\xab\\xe1\\x83\\xa6\\xe1\\x83\\x9a\\xe1\\x83\\x98\\xe1\\x83\\xa1\\xe1\\x83\\x90\\x20\\xe1\\x83\\x97\\xe1\\x83\\x90\\xe1\\x83\\x95\\xe1\\x83\\x96\\xe1\\x83\\x94\\x2e\";\n    static const char* ku_sentence_utf8 = \"\\xd8\\xb4\\xdb\\x8e\\xd9\\x88\\xd9\\x86\\xd8\\xa7\\x20\\xdb\\x8c\\xd9\\x87\\xda\\xa9\\xda\\xaf\\xd8\\xa7\\xdb\\x95\\xd8\\xb1\\xd8\\xa7\\xd9\\x86\\xdb\\x8e\\xd9\\x85\\xd8\\xa7\\xd9\\x86\\xdb\\x8c\\x20\\xd8\\xa8\\xd9\\x88\\xd9\\x88\\xd9\\x86\\xd8\\xaf\\xd9\\x88\\xd9\\x88\\xd9\\x85\\xd8\\xaf\\xd8\\xb1\\xd8\\xa7\\xd9\\x88\\xda\\x98\\xd8\\xa7\\xd9\\x86\\x20\\xd8\\xb4\\xda\\xa9\\xd8\\xb1\\xdb\\x95\\x20\\xd8\\xb4\\xd8\\xa7\\xd9\\x85\\xd8\\xa7\\xd8\\xaa\\xd8\\xaf\\xd8\\xb1\\xd8\\xa7\\x2e\";\n    static const char* ps_sentence_utf8 = \"\\xd9\\x88\\xd9\\x8a\\xd9\\x88\\xd9\\x88\\x20\\xd9\\x85\\xd9\\x84\\xd8\\xb3\\xd9\\x8a\\x20\\xd9\\x85\\xd9\\x84\\xd8\\xb3\\x20\\xd9\\x88\\xd9\\x85\\xd9\\x84\\xd8\\xb3\\xd8\\xaa\\x20\\xd9\\x85\\xd8\\xb2\\xd8\\xb3\\xd9\\x85\\xd9\\x85\\xd8\\xaa\\xd9\\x8a\\xd8\\xaf\\xd9\\x8a\\xd8\\xac\\xd8\\xa8\\x20\\xd9\\x85\\xd8\\xb1\\xd9\\x88\\xd9\\x8a\\xd9\\x88\\xd8\\xb3\\x20\\xd8\\xa8\\xd9\\x87\\xd8\\xaa\\xd8\\xb1\\xd9\\x8a\\xd9\\x86\\xd8\\xaa\\xd8\\xb1\\xd9\\x8a\\xd9\\x86\\xd9\\x86\\xd9\\x88\\xd8\\xaf\\xd9\\x88\\xd8\\xa8\\xd8\\xa7\\xd9\\x84\\xd9\\x87\\xd8\\x9f\";\n    static const char* so_sentence_utf8 = \"\\x43\\x69\\x64\\x69\\x20\\x77\\x61\\x78\\x61\\x79\\x20\\x64\\x61\\x72\\x20\\x77\\x61\\x61\\x20\\x67\\x61\\x6c\\x6d\\x75\\x64\\x61\\x20\\x77\\x69\\x20\\x71\\x61\\x61\\x20\\x6d\\x69\\x64\\x61\\x20\\x63\\x69\\x64\\x69\\x2e\";\n    static const char* uz_sentence_utf8 = \"\\x42\\x69\\x72\\x20\\x6B\\x75\\x63\\x68\\x20\\x66\\x72\\x61\\x7A\\x61\\x73\\x69\\x20\\x61\\x79\\x20\\x64\\x75\\x6E\\x79\\x6F\\x6B\\x6C\\x61\\x72\\x69\\x20\\x6D\\x65\\x6E\\x20\\x62\\x69\\x6B\\x6F\\x72\\x20\\x74\\x75\\x6E\\x20\\x6F\\x6C\\x64\\x69\\x72\\x20\\x67\\x69\\x6D\\x70\\x6F\\x6B\\x20\\x62\\x69\\x6C\\x61\\x6E\\x64\\x61\\x20\\x76\\x61\\x20\\x6B\\x6F\\x6E\\x75\\x70\\x20\\x79\\x69\\x6C\\x64\\x69\\x7A\\x2E\";\n    static const char* az_sentence_utf8 = \"\\x42\\x75\\x74\\x75\\x6E\\x20\\x6B\\x69\\x72\\x70\\x61\\x70\\x79\\x61\\x20\\x74\\xC9\\x99\\x6D\\x69\\x7A\\x20\\x6D\\xC9\\x99\\x68\\x73\\x75\\x6C\\x6F\\x6C\\x61\\x72\\x20\\x62\\x61\\x78\\x74\\x61\\x20\\x62\\x61\\x68\\x61\\x72\\x61\\x74\\x20\\x71\\xC9\\x99\\x7A\\xC9\\x99\\x6B\\x61\\x6E\\x6E\\xC9\\x99\\x20\\x61\\x79\\xC4\\xB1\\x72\\x20\\x6F\\x74\\x75\\x7A\\x20\\x6C\\xC9\\x99\\x20\\x6C\\xC9\\x99\\x73\\x74\\x69\\x62\\x2E\";\n    static const char* hy_sentence_utf8 = \"\\xD4\\xB2\\xD5\\xA1\\xD6\\x80\\xD5\\xA6\\x20\\xD5\\xA5\\xD6\\x84\\x20\\xD5\\xAB\\xD5\\xA1\\xD5\\xBA\\xD5\\xB0\\xD5\\xB6\\xD5\\xB5\\x20\\xD5\\xA2\\xD5\\xA1\\xD5\\xB2\\xD5\\xA8\\xD5\\xB5\\xD5\\xAF\\xD5\\xB8\\xD5\\xB2\\xD5\\xBA\\xD5\\xA1\\xD5\\xB0\\xD5\\xAB\\xD5\\xAC\\x20\\xD5\\xA1\\xD5\\xB7\\xD5\\xAD\\xD5\\xA1\\xD5\\xAC\\xD5\\xAB\\x20\\xD5\\xBD\\xD5\\xB6\\xD5\\xB3\\x20\\xD5\\xB8\\xD5\\xA9\\xD5\\xAB\";\n    static const char* ja_sentence_utf8 = \"\\xe3\\x82\\xa2\\xe3\\x82\\xa4\\xe3\\x82\\xa6\\x20\\xe3\\x82\\xa8\\xe3\\x82\\xaa\\x20\\xe3\\x82\\xab\\xe3\\x82\\xad\\xe3\\x82\\xaf\\x20\\xe3\\x82\\xb1\\xe3\\x82\\xb3\\x20\\xe3\\x82\\xb5\\xe3\\x82\\xb7\\xe3\\x82\\xb9\\x20\\xe3\\x82\\xbb\\xe3\\x82\\xbd\\x20\\xe3\\x82\\xbf\\xe3\\x82\\xb9\\xe3\\x82\\xbd\\x20\\xe3\\x82\\xbb\\xe3\\x82\\xbd\\x20\\xe3\\x83\\x88\\xe3\\x82\\xbd\\xe3\\x83\\x88\\x20\\xe3\\x83\\x88\\xe3\\x83\\xaa\\xe3\\x83\\x88\\x20\\xe3\\x83\\x88\\xe3\\x82\\xbd\\xe3\\x83\\x88\\x20\\xe3\\x83\\x8f\\xe3\\x83\\xaa\\xe3\\x83\\x88\\x20\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\xe3\\x83\\x8f\\x20\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\xe3\\x82\\xbd\\x20\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\x20\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\xe3\\x83\\x8f\\x20\\xe3\\x83\\x8f\\xe3\\x82\\xbd\\xe3\\x82\\xbd\";\n    static const char* zh_sentence_utf8 = \"\\xe4\\xb8\\xad\\xe6\\x96\\x87\\xe4\\xb8\\xad\\xe7\\x9a\\x84\\xe6\\xaf\\x8f\\xe4\\xb8\\xaa\\xe5\\xad\\x97\\xe6\\xaf\\x8d\\xe9\\x83\\xbd\\xe5\\xbe\\x88\\xe9\\x87\\x8d\\xe8\\xa6\\x81\\xef\\xbc\\x8c\\xe5\\xae\\x83\\xe4\\xbb\\xac\\xe5\\x85\\xb1\\xe5\\x90\\x8c\\xe6\\x9e\\x84\\xe6\\x88\\x90\\xe4\\xba\\x86\\xe5\\x8f\\xa5\\xe5\\xad\\x90\\xe3\\x80\\x82\";\n    struct {\n        const char* id; // language ID\n        const char* s;  // sentence\n    } sentences[] = {\n        {\"en\", en_sentence_utf8},  {\"es\", es_sentence_utf8},  {\"fr\", fr_sentence_utf8},\n        {\"de\", de_sentence_utf8},  {\"ru\", ru_sentence_utf8},  {\"pt\", pt_sentence_utf8},\n        {\"it\", it_sentence_utf8},  {\"nl\", nl_sentence_utf8},  {\"el\", el_sentence_utf8},\n        {\"tr\", tr_sentence_utf8},  {\"pl\", pl_sentence_utf8},  {\"uk\", uk_sentence_utf8},\n        {\"cs\", cs_sentence_utf8},  {\"hu\", hu_sentence_utf8},  {\"bg\", bg_sentence_utf8},\n        {\"sr\", sr_sentence_utf8},  {\"sq\", sq_sentence_utf8},  {\"sl\", sl_sentence_utf8},\n        {\"lt\", lt_sentence_utf8},  {\"lv\", lv_sentence_utf8},  {\"et\", et_sentence_utf8},\n        {\"ga\", ga_sentence_utf8},  {\"cy\", cy_sentence_utf8},  {\"is\", is_sentence_utf8},\n        {\"mt\", mt_sentence_utf8},  {\"he\", he_sentence_utf8},  {\"ar\", ar_sentence_utf8},\n        {\"hi\", hi_sentence_utf8},  {\"bn\", bn_sentence_utf8},  {\"ta\", ta_sentence_utf8},\n        {\"te\", te_sentence_utf8},  {\"kn\", kn_sentence_utf8},  {\"ml\", ml_sentence_utf8},\n        {\"si\", si_sentence_utf8},  {\"th\", th_sentence_utf8},  {\"lo\", lo_sentence_utf8},\n        {\"ka\", ka_sentence_utf8},  {\"ku\", ku_sentence_utf8},  {\"ps\", ps_sentence_utf8},\n        {\"so\", so_sentence_utf8},  {\"uz\", uz_sentence_utf8},  {\"az\", az_sentence_utf8},\n        {\"hy\", hy_sentence_utf8},  {\"ja\", ja_sentence_utf8},  {\"zh\", zh_sentence_utf8}\n    };\n    for (int i = 0; i < (int)(sizeof(sentences) / sizeof(sentences[0])); i++) {\n        end = ui_edit_text.end_range(t);\n//      rt_println(\"%s %s\", sentences[i].id, sentences[i].s);\n        rt_swear(ui_edit_text.replace_utf8(t, &end, sentences[i].s, -1, null));\n        rt_swear(ui_edit_text.replace_utf8(t, &end, \"\\n\\n\", -1, null));\n    }\n    static const char* pirate_flag_utf8 = \"\\xF0\\x9F\\x8F\\xB4\\xE2\\x80\\x8D\\xE2\\x98\\xA0\\xEF\\xB8\\x8F\";\n    end = ui_edit_text.end_range(t);\n    rt_swear(ui_edit_text.replace_utf8(t, &end, pirate_flag_utf8, -1, null));\n#endif\n}\n"
  },
  {
    "path": "src/samples/groot/groot.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"rocket.h\"\n#include \"groot.h\"\n#include \"stb_image.h\"\n\n// TODO: stack(ui_text with the content \"I am groot..\", view_groot)\n// Top: Find single line edit control \"groot\" with Find button that selects found text\n// bottom: UTC time and local time status of all views?,\n// Right view: debug toggles (text metric and margins) + message box on close window:\n// \"Groot: I am groot...\\n\"\n// \"Rocket: He says: Where the heck are you going now?\\n\"\n// \"Retry\" \"Abort\" \"Ignore\"\n\n\nenum { width = 512, height = 512 };\n\nstatic uint8_t gs[width * height]; // greyscale\n//static ui_bitmap_t image; // grayscale image\n\nstatic ui_image_t view_groot;\nstatic ui_image_t view_rocket;\nstatic ui_image_t view_gs[2]; // two views at the same image\n\nstatic ui_edit_view_t  view_text;\nstatic ui_edit_doc_t   document;\n\nstatic void* load_image(const uint8_t* data, int64_t bytes, int32_t* w, int32_t* h,\n    int32_t* bpp, int32_t preferred_bytes_per_pixel) {\n    void* pixels = stbi_load_from_memory((uint8_t const*)data, (int32_t)bytes, w, h,\n        bpp, preferred_bytes_per_pixel);\n    return pixels;\n}\n\nstatic void init_image(ui_bitmap_t* i, const uint8_t* data, int64_t bytes) {\n    int32_t w = 0;\n    int32_t h = 0;\n    int32_t c = 0;\n    void* pixels = load_image(data, bytes, &w, &h, &c, 0);\n    rt_not_null(pixels);\n    ui_gdi.bitmap_init(i, w, h, c, pixels);\n    stbi_image_free(pixels);\n}\n\nstatic void init_gs(void) {\n    const ui_bitmap_t* i = &view_groot.image;\n    uint32_t* pixels = (uint32_t*)i->pixels;\n    rt_assert(i->w == 64 && i->h == 64 && i->bpp == 4);\n    for (int y = 0; y < height; y++) {\n        int32_t y64 = y % 64;\n        for (int x = 0; x < width; x++) {\n            int32_t x64 = x % 64;\n            uint32_t rgba = pixels[y64 * 64 + x64];\n            ui_color_t c = (ui_color_t)rgba;\n            c = ui_colors.gray_with_same_intensity(c);\n            gs[y * width + x] = ((x / 64) % 2) == ((y / 64) % 2) ?\n                (uint8_t)(c & 0xFF) : 0xC0;\n        }\n    }\n}\n\nstatic void panel_erase(ui_view_t* v) {\n    ui_gdi.frame(v->x + 1, v->y + 1, v->w - 1, v->h - 1, ui_colors.black);\n}\n\nstatic void gs_erase(ui_view_t* v) {\n    ui_gdi.fill(v->x, v->y, v->w, v->h, ui_colors.ennui_black);\n}\n\nstatic void slider_format(ui_view_t* v) {\n    ui_slider_t* s = (ui_slider_t*)v;\n    ui_view.set_text(v, \"%.0f%%\", s->value * 100.0 / 255.0);\n}\n\nstatic void slider_callback(ui_view_t* v) {\n    ui_slider_t* s = (ui_slider_t*)v;\n    view_groot.alpha = (fp64_t)s->value / 256.0;\n//  rt_println(\"value: %d\", slider->value);\n}\n\nstatic void init_images(void) {\n    ui_image.init(&view_groot);\n    init_image(&view_groot.image, groot, rt_countof(groot));\n    // view of groot image:\n//  ui_image.ratio(&view_groot, 4, 1); // 4:1\n    ui_image.ratio(&view_groot, 3, 1); // 4:1\n    view_groot.alpha = 0.5;\n    view_groot.padding = (ui_margins_t){0.125f, 0.125f, 0.125f, 0.125f};\n    view_groot.focusable = false; // because it is stacked under text editor\n    // view of rocket image:\n    ui_image.init(&view_rocket);\n    init_image(&view_rocket.image, rocket, rt_countof(rocket));\n    ui_image.ratio(&view_rocket, 3, 1); // 3:1\n    view_rocket.padding = (ui_margins_t){0.125f, 0.125f, 0.125f, 0.125f};\n    view_groot.focusable = false; // no zoom/pan\n    init_gs();\n}\n\nstatic void init_text(void) {\n    rt_swear(ui_edit_doc.init(&document,\n    \"Star-Lord: \\\"What is wrong with giving tree, here?\\\"\\n\"\n    \"Rocket: \\\"Well, he don't know talking good like me and you, \"\n              \"so his vocabulistics is limited to 'I' and 'am' and 'Groot.' \"\n              \"Exclusively, in that order.\\\"\", -1, false));\n    ui_edit_view.init(&view_text, &document);\n    view_text.hide_word_wrap = true;\nview_text.hide_word_wrap = false; // TODO: debugging remove\n    view_text.padding = (ui_margins_t){0};\n// TODO: commented out for debugging uncomment is hiding word wrap\n//  view_text.insets = (ui_margins_t){0};\n    view_text.background_id = 0;\n    view_text.background = ui_colors.transparent;\n    rt_str_printf(view_text.hint,\n        \"Text Edit:\\n\\n\"\n        \"Try double clicking to select a word\\n\"\n        \"or long-pressing to select a paragraph.\\n\\n\"\n        \"Ctrl+[Shift]+arrows, Ctrl+X|C|V,\\n\"\n        \"Undo/Redo Ctrl+Z/Y\\n\"\n    );\n}\n\nstatic ui_view_t* align(ui_view_t* v, int32_t align) {\n    v->align = align;\n    return v;\n}\n\nstatic ui_view_t* fill_parent(ui_view_t* v) {\n    v->max_h  = ui.infinity;\n    v->max_w  = ui.infinity;\n    return v;\n}\n\nstatic void opened(void) {\n    init_images();\n    init_text();\n    static ui_view_t  list         = ui_view(list);\n    static ui_label_t label_left   = ui_label(0, \"Left\");\n    static ui_label_t label_top    = ui_label(0, \"Top\");\n    static ui_label_t label_bottom = ui_label(0, \"Bottom\");\n    // painting greyscale pixels will be handled w/o device bitmap:\n    for (int32_t i = 0; i < rt_countof(view_gs); i++) {\n        ui_image.init_with(&view_gs[i], gs, width, height, 1, width);\n        view_gs[i].erase = gs_erase;\n        view_gs[i].focusable = true; // enable zoom pan\n    }\n    static ui_view_t   top    = ui_view(stack);\n    static ui_view_t   center = ui_view(span);\n    static ui_view_t   left   = ui_view(list);\n    static ui_view_t   right  = ui_view(list);\n    static ui_view_t   stack  = ui_view(stack);\n    static ui_view_t   bottom = ui_view(stack);\n    static ui_view_t   spacer = ui_view(spacer);\n    static ui_slider_t slider = ui_slider(\"128\", 16.0f, 0, 255,\n            slider_format, slider_callback);\n    slider.value = 128;\n    ui_view.add(fill_parent(&left),\n                align(&view_gs[0].view, ui.align.left),\n                align(&view_gs[1].view, ui.align.left),\n                null\n    );\n    ui_view.add(&stack,\n                fill_parent(&view_groot.view),\n                fill_parent(&view_text.view),\n                null\n    );\n    ui_view.add(&right,\n                &spacer,\n                fill_parent(&view_rocket.view),\n                &slider,\n                fill_parent(&stack),\n                null\n    );\n    ui_view.add(&top,    &label_top,    null);\n    ui_view.add(&bottom, &label_bottom, null);\n    ui_view.add(&center,\n                align(&left,  ui.align.top),\n                align(&right, ui.align.top),\n                null);\n    ui_view.add(ui_app.content,\n        ui_view.add(fill_parent(&list),\n                    align(&top,    ui.align.center),\n                    align(&center, ui.align.left),\n                    align(&bottom, ui.align.center),\n                    null\n        ),\n        null\n    );\n    stack.debug.id = \"#stack: edit+image\";\n    list.debug.id = \"#list\";\n    right.debug.id = \"#right\";\n//  list.debug.paint.margins = true;\nright.debug.paint.margins = true;\n    center.insets  = (ui_margins_t){0};\n    center.padding = (ui_margins_t){0};\n    static ui_view_t* panels[] = { &top, &left, &right, &bottom  };\n    for (int32_t i = 0; i < rt_countof(panels); i++) {\n        panels[i]->erase = panel_erase;\n        panels[i]->padding = (ui_margins_t){0};\n        panels[i]->insets  = (ui_margins_t){0.125f, 0.125f, 0.125f, 0.125f};\n    }\n    list.background_id = ui_color_id_window;\n    ui_view_for_each(&list, it, {\n//      it->debug.paint.margins = true;\n        it->max_w   = ui.infinity;\n    });\n    ui_view_for_each(&center, it, {\n//      it->debug.paint.margins = true;\n        it->max_h   = ui.infinity;\n    });\n    center.max_h = ui.infinity;\n    for (int32_t i = 0; i < rt_countof(view_gs); i++) {\n        fill_parent(&view_gs[i].view)->erase = panel_erase;\n    }\n    view_gs[0].padding.bottom = 0.25f;\n    view_gs[1].padding.top    = 0.25f;\n    view_gs[0].fit  = true;\n    view_gs[1].fill = true;\n    view_groot.debug.id  = \"#view.groot\";\n    view_rocket.debug.id = \"#view.rocket\";\n\n    view_groot.fit = true;\n    view_rocket.fit = true;\n\n//  view_groot.fill = true;\n//  view_rocket.fill = true;\n}\n\nstatic void closed(void) {\n    ui_view.disband(ui_app.content);\n    ui_gdi.bitmap_dispose(&view_groot.image);\n    ui_gdi.bitmap_dispose(&view_rocket.image);\n}\n\nui_app_t ui_app = {\n    .class_name = \"groot\",\n    .title = \"Groot\",\n    .dark_mode = true,\n    .opened = opened,\n    .closed = closed,\n    .window_sizing = { // inches\n        .min_w = 5.0f,\n        .min_h = 4.0f,\n        .ini_w = 10.0f,\n        .ini_h = 6.0f\n    }\n};\n"
  },
  {
    "path": "src/samples/groot/groot.h",
    "content": "#pragma once\n\nstatic unsigned char groot[] = { // groot.jpg file 64x64 rgba\n     0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,\n     0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,\n     0x08, 0x03, 0x00, 0x00, 0x00, 0x9D, 0xB7, 0x81, 0xEC, 0x00, 0x00, 0x00,\n     0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61,\n     0x05, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE,\n     0x1C, 0xE9, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,\n     0x2E, 0x23, 0x00, 0x00, 0x2E, 0x23, 0x01, 0x78, 0xA5, 0x3F, 0x76, 0x00,\n     0x00, 0x13, 0xAE, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4D, 0x4C, 0x3A, 0x63,\n     0x6F, 0x6D, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x78, 0x6D, 0x70,\n     0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B,\n     0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB,\n     0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D, 0x22, 0x57, 0x35, 0x4D, 0x30, 0x4D,\n     0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7A, 0x72, 0x65, 0x53, 0x7A, 0x4E,\n     0x54, 0x63, 0x7A, 0x6B, 0x63, 0x39, 0x64, 0x22, 0x3F, 0x3E, 0x0A, 0x3C,\n     0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6D,\n     0x6C, 0x6E, 0x73, 0x3A, 0x78, 0x3D, 0x22, 0x61, 0x64, 0x6F, 0x62, 0x65,\n     0x3A, 0x6E, 0x73, 0x3A, 0x6D, 0x65, 0x74, 0x61, 0x2F, 0x22, 0x20, 0x78,\n     0x3A, 0x78, 0x6D, 0x70, 0x74, 0x6B, 0x3D, 0x22, 0x58, 0x4D, 0x50, 0x20,\n     0x43, 0x6F, 0x72, 0x65, 0x20, 0x34, 0x2E, 0x34, 0x2E, 0x30, 0x2D, 0x45,\n     0x78, 0x69, 0x76, 0x32, 0x22, 0x3E, 0x0A, 0x20, 0x3C, 0x72, 0x64, 0x66,\n     0x3A, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x72,\n     0x64, 0x66, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77,\n     0x77, 0x77, 0x2E, 0x77, 0x33, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x31, 0x39,\n     0x39, 0x39, 0x2F, 0x30, 0x32, 0x2F, 0x32, 0x32, 0x2D, 0x72, 0x64, 0x66,\n     0x2D, 0x73, 0x79, 0x6E, 0x74, 0x61, 0x78, 0x2D, 0x6E, 0x73, 0x23, 0x22,\n     0x3E, 0x0A, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x44, 0x65, 0x73,\n     0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x72, 0x64, 0x66,\n     0x3A, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x3D, 0x22, 0x22, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x78, 0x6D, 0x70, 0x4D,\n     0x4D, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73,\n     0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78,\n     0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x6D, 0x6D, 0x2F, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x73, 0x74,\n     0x45, 0x76, 0x74, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F,\n     0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D,\n     0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x73, 0x54, 0x79,\n     0x70, 0x65, 0x2F, 0x52, 0x65, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x45,\n     0x76, 0x65, 0x6E, 0x74, 0x23, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x78,\n     0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3D, 0x22,\n     0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64,\n     0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F,\n     0x31, 0x2E, 0x30, 0x2F, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2F, 0x52, 0x65,\n     0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, 0x23, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x64, 0x63,\n     0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x70, 0x75, 0x72,\n     0x6C, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x64, 0x63, 0x2F, 0x65, 0x6C, 0x65,\n     0x6D, 0x65, 0x6E, 0x74, 0x73, 0x2F, 0x31, 0x2E, 0x31, 0x2F, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x65, 0x78,\n     0x69, 0x66, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E,\n     0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F,\n     0x65, 0x78, 0x69, 0x66, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x22, 0x0A, 0x20,\n     0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x47, 0x49, 0x4D,\n     0x50, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77,\n     0x77, 0x2E, 0x67, 0x69, 0x6D, 0x70, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x78,\n     0x6D, 0x70, 0x2F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C,\n     0x6E, 0x73, 0x3A, 0x70, 0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70,\n     0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E,\n     0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x68,\n     0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A,\n     0x74, 0x69, 0x66, 0x66, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F,\n     0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F,\n     0x6D, 0x2F, 0x74, 0x69, 0x66, 0x66, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x22,\n     0x0A, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x78,\n     0x6D, 0x70, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E,\n     0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F,\n     0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x22, 0x0A, 0x20, 0x20,\n     0x20, 0x78, 0x6D, 0x70, 0x4D, 0x4D, 0x3A, 0x44, 0x6F, 0x63, 0x75, 0x6D,\n     0x65, 0x6E, 0x74, 0x49, 0x44, 0x3D, 0x22, 0x61, 0x64, 0x6F, 0x62, 0x65,\n     0x3A, 0x64, 0x6F, 0x63, 0x69, 0x64, 0x3A, 0x70, 0x68, 0x6F, 0x74, 0x6F,\n     0x73, 0x68, 0x6F, 0x70, 0x3A, 0x35, 0x31, 0x33, 0x61, 0x64, 0x63, 0x66,\n     0x64, 0x2D, 0x35, 0x65, 0x62, 0x30, 0x2D, 0x35, 0x35, 0x34, 0x35, 0x2D,\n     0x38, 0x39, 0x32, 0x37, 0x2D, 0x35, 0x36, 0x30, 0x65, 0x32, 0x30, 0x61,\n     0x65, 0x39, 0x32, 0x34, 0x38, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x78, 0x6D,\n     0x70, 0x4D, 0x4D, 0x3A, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65,\n     0x49, 0x44, 0x3D, 0x22, 0x78, 0x6D, 0x70, 0x2E, 0x69, 0x69, 0x64, 0x3A,\n     0x31, 0x32, 0x31, 0x62, 0x64, 0x37, 0x39, 0x63, 0x2D, 0x30, 0x63, 0x63,\n     0x32, 0x2D, 0x34, 0x64, 0x65, 0x61, 0x2D, 0x62, 0x31, 0x37, 0x37, 0x2D,\n     0x39, 0x62, 0x33, 0x61, 0x30, 0x63, 0x66, 0x66, 0x63, 0x63, 0x62, 0x65,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x70, 0x4D, 0x4D, 0x3A, 0x4F,\n     0x72, 0x69, 0x67, 0x69, 0x6E, 0x61, 0x6C, 0x44, 0x6F, 0x63, 0x75, 0x6D,\n     0x65, 0x6E, 0x74, 0x49, 0x44, 0x3D, 0x22, 0x36, 0x38, 0x38, 0x45, 0x45,\n     0x41, 0x46, 0x37, 0x30, 0x30, 0x30, 0x37, 0x44, 0x31, 0x35, 0x41, 0x41,\n     0x38, 0x30, 0x45, 0x37, 0x45, 0x42, 0x30, 0x33, 0x39, 0x36, 0x41, 0x39,\n     0x41, 0x44, 0x43, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x64, 0x63, 0x3A, 0x66,\n     0x6F, 0x72, 0x6D, 0x61, 0x74, 0x3D, 0x22, 0x69, 0x6D, 0x61, 0x67, 0x65,\n     0x2F, 0x70, 0x6E, 0x67, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69,\n     0x66, 0x3A, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x53, 0x70, 0x61, 0x63, 0x65,\n     0x3D, 0x22, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66,\n     0x3A, 0x45, 0x78, 0x69, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E,\n     0x3D, 0x22, 0x30, 0x32, 0x32, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x65,\n     0x78, 0x69, 0x66, 0x3A, 0x50, 0x69, 0x78, 0x65, 0x6C, 0x58, 0x44, 0x69,\n     0x6D, 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x39, 0x32, 0x30,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3A, 0x50, 0x69,\n     0x78, 0x65, 0x6C, 0x59, 0x44, 0x69, 0x6D, 0x65, 0x6E, 0x73, 0x69, 0x6F,\n     0x6E, 0x3D, 0x22, 0x38, 0x33, 0x33, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x47,\n     0x49, 0x4D, 0x50, 0x3A, 0x41, 0x50, 0x49, 0x3D, 0x22, 0x32, 0x2E, 0x30,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x3A, 0x50, 0x6C,\n     0x61, 0x74, 0x66, 0x6F, 0x72, 0x6D, 0x3D, 0x22, 0x57, 0x69, 0x6E, 0x64,\n     0x6F, 0x77, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x47, 0x49, 0x4D, 0x50,\n     0x3A, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x3D, 0x22,\n     0x31, 0x36, 0x35, 0x31, 0x32, 0x37, 0x31, 0x32, 0x31, 0x34, 0x38, 0x31,\n     0x34, 0x32, 0x38, 0x32, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x47, 0x49, 0x4D,\n     0x50, 0x3A, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x32,\n     0x2E, 0x31, 0x30, 0x2E, 0x33, 0x30, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x70,\n     0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70, 0x3A, 0x43, 0x6F, 0x6C,\n     0x6F, 0x72, 0x4D, 0x6F, 0x64, 0x65, 0x3D, 0x22, 0x33, 0x22, 0x0A, 0x20,\n     0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x49, 0x6D, 0x61, 0x67, 0x65,\n     0x4C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3D, 0x22, 0x38, 0x33, 0x33, 0x22,\n     0x0A, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x49, 0x6D, 0x61,\n     0x67, 0x65, 0x57, 0x69, 0x64, 0x74, 0x68, 0x3D, 0x22, 0x39, 0x32, 0x30,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x4F, 0x72,\n     0x69, 0x65, 0x6E, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x50, 0x68,\n     0x6F, 0x74, 0x6F, 0x6D, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6E, 0x74,\n     0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x3D,\n     0x22, 0x32, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A,\n     0x52, 0x65, 0x73, 0x6F, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x55, 0x6E,\n     0x69, 0x74, 0x3D, 0x22, 0x32, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x74, 0x69,\n     0x66, 0x66, 0x3A, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x73, 0x50, 0x65,\n     0x72, 0x50, 0x69, 0x78, 0x65, 0x6C, 0x3D, 0x22, 0x33, 0x22, 0x0A, 0x20,\n     0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x58, 0x52, 0x65, 0x73, 0x6F,\n     0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x33, 0x30, 0x30, 0x2F,\n     0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x59,\n     0x52, 0x65, 0x73, 0x6F, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x22,\n     0x33, 0x30, 0x30, 0x2F, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x78, 0x6D,\n     0x70, 0x3A, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x65,\n     0x3D, 0x22, 0x32, 0x30, 0x31, 0x39, 0x2D, 0x30, 0x38, 0x2D, 0x32, 0x34,\n     0x54, 0x31, 0x31, 0x3A, 0x30, 0x33, 0x3A, 0x33, 0x37, 0x2B, 0x30, 0x35,\n     0x3A, 0x33, 0x30, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x78, 0x6D, 0x70, 0x3A,\n     0x43, 0x72, 0x65, 0x61, 0x74, 0x6F, 0x72, 0x54, 0x6F, 0x6F, 0x6C, 0x3D,\n     0x22, 0x47, 0x49, 0x4D, 0x50, 0x20, 0x32, 0x2E, 0x31, 0x30, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x78, 0x6D, 0x70, 0x3A, 0x4D, 0x65, 0x74, 0x61, 0x64,\n     0x61, 0x74, 0x61, 0x44, 0x61, 0x74, 0x65, 0x3D, 0x22, 0x32, 0x30, 0x31,\n     0x39, 0x2D, 0x30, 0x38, 0x2D, 0x32, 0x34, 0x54, 0x31, 0x31, 0x3A, 0x30,\n     0x36, 0x3A, 0x33, 0x32, 0x2B, 0x30, 0x35, 0x3A, 0x33, 0x30, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x78, 0x6D, 0x70, 0x3A, 0x4D, 0x6F, 0x64, 0x69, 0x66,\n     0x79, 0x44, 0x61, 0x74, 0x65, 0x3D, 0x22, 0x32, 0x30, 0x31, 0x39, 0x2D,\n     0x30, 0x38, 0x2D, 0x32, 0x34, 0x54, 0x31, 0x31, 0x3A, 0x30, 0x36, 0x3A,\n     0x33, 0x32, 0x2B, 0x30, 0x35, 0x3A, 0x33, 0x30, 0x22, 0x3E, 0x0A, 0x20,\n     0x20, 0x20, 0x3C, 0x78, 0x6D, 0x70, 0x4D, 0x4D, 0x3A, 0x48, 0x69, 0x73,\n     0x74, 0x6F, 0x72, 0x79, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72,\n     0x64, 0x66, 0x3A, 0x53, 0x65, 0x71, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x0A, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x61, 0x63, 0x74,\n     0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x73, 0x61, 0x76, 0x65, 0x64, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A,\n     0x63, 0x68, 0x61, 0x6E, 0x67, 0x65, 0x64, 0x3D, 0x22, 0x2F, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A,\n     0x69, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x49, 0x44, 0x3D, 0x22,\n     0x78, 0x6D, 0x70, 0x2E, 0x69, 0x69, 0x64, 0x3A, 0x36, 0x66, 0x32, 0x32,\n     0x36, 0x33, 0x34, 0x33, 0x2D, 0x39, 0x62, 0x35, 0x65, 0x2D, 0x37, 0x36,\n     0x34, 0x36, 0x2D, 0x61, 0x32, 0x30, 0x62, 0x2D, 0x35, 0x64, 0x31, 0x39,\n     0x62, 0x66, 0x61, 0x30, 0x30, 0x31, 0x66, 0x33, 0x22, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x73, 0x6F,\n     0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x41, 0x67, 0x65, 0x6E, 0x74, 0x3D,\n     0x22, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6F, 0x74, 0x6F,\n     0x73, 0x68, 0x6F, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x57, 0x69, 0x6E,\n     0x64, 0x6F, 0x77, 0x73, 0x29, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x77, 0x68, 0x65, 0x6E, 0x3D,\n     0x22, 0x32, 0x30, 0x31, 0x39, 0x2D, 0x30, 0x38, 0x2D, 0x32, 0x34, 0x54,\n     0x31, 0x31, 0x3A, 0x30, 0x36, 0x3A, 0x33, 0x32, 0x2B, 0x30, 0x35, 0x3A,\n     0x33, 0x30, 0x22, 0x2F, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C,\n     0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x61, 0x63, 0x74, 0x69, 0x6F,\n     0x6E, 0x3D, 0x22, 0x63, 0x6F, 0x6E, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76,\n     0x74, 0x3A, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73,\n     0x3D, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x69, 0x6D, 0x61, 0x67, 0x65,\n     0x2F, 0x6A, 0x70, 0x65, 0x67, 0x20, 0x74, 0x6F, 0x20, 0x69, 0x6D, 0x61,\n     0x67, 0x65, 0x2F, 0x70, 0x6E, 0x67, 0x22, 0x2F, 0x3E, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x0A, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x61,\n     0x63, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x64, 0x65, 0x72, 0x69, 0x76,\n     0x65, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74,\n     0x45, 0x76, 0x74, 0x3A, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65,\n     0x72, 0x73, 0x3D, 0x22, 0x63, 0x6F, 0x6E, 0x76, 0x65, 0x72, 0x74, 0x65,\n     0x64, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x69, 0x6D, 0x61, 0x67, 0x65,\n     0x2F, 0x6A, 0x70, 0x65, 0x67, 0x20, 0x74, 0x6F, 0x20, 0x69, 0x6D, 0x61,\n     0x67, 0x65, 0x2F, 0x70, 0x6E, 0x67, 0x22, 0x2F, 0x3E, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x0A, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x61,\n     0x63, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x73, 0x61, 0x76, 0x65, 0x64,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76,\n     0x74, 0x3A, 0x63, 0x68, 0x61, 0x6E, 0x67, 0x65, 0x64, 0x3D, 0x22, 0x2F,\n     0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76,\n     0x74, 0x3A, 0x69, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x49, 0x44,\n     0x3D, 0x22, 0x78, 0x6D, 0x70, 0x2E, 0x69, 0x69, 0x64, 0x3A, 0x36, 0x66,\n     0x32, 0x34, 0x33, 0x65, 0x32, 0x61, 0x2D, 0x62, 0x66, 0x38, 0x31, 0x2D,\n     0x34, 0x63, 0x34, 0x61, 0x2D, 0x62, 0x37, 0x62, 0x32, 0x2D, 0x36, 0x36,\n     0x62, 0x35, 0x32, 0x38, 0x34, 0x64, 0x36, 0x32, 0x38, 0x65, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A,\n     0x73, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x41, 0x67, 0x65, 0x6E,\n     0x74, 0x3D, 0x22, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6F,\n     0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70, 0x20, 0x43, 0x43, 0x20, 0x28, 0x57,\n     0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x29, 0x22, 0x0A, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x77, 0x68, 0x65,\n     0x6E, 0x3D, 0x22, 0x32, 0x30, 0x31, 0x39, 0x2D, 0x30, 0x38, 0x2D, 0x32,\n     0x34, 0x54, 0x31, 0x31, 0x3A, 0x30, 0x36, 0x3A, 0x33, 0x32, 0x2B, 0x30,\n     0x35, 0x3A, 0x33, 0x30, 0x22, 0x2F, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x0A, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x61, 0x63, 0x74,\n     0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x73, 0x61, 0x76, 0x65, 0x64, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A,\n     0x63, 0x68, 0x61, 0x6E, 0x67, 0x65, 0x64, 0x3D, 0x22, 0x2F, 0x22, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A,\n     0x69, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x49, 0x44, 0x3D, 0x22,\n     0x78, 0x6D, 0x70, 0x2E, 0x69, 0x69, 0x64, 0x3A, 0x66, 0x61, 0x30, 0x64,\n     0x63, 0x37, 0x66, 0x66, 0x2D, 0x35, 0x64, 0x36, 0x66, 0x2D, 0x34, 0x64,\n     0x65, 0x34, 0x2D, 0x61, 0x62, 0x39, 0x31, 0x2D, 0x30, 0x66, 0x38, 0x62,\n     0x63, 0x61, 0x61, 0x63, 0x38, 0x63, 0x33, 0x32, 0x22, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x73, 0x6F,\n     0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x41, 0x67, 0x65, 0x6E, 0x74, 0x3D,\n     0x22, 0x47, 0x69, 0x6D, 0x70, 0x20, 0x32, 0x2E, 0x31, 0x30, 0x20, 0x28,\n     0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x29, 0x22, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3A, 0x77, 0x68,\n     0x65, 0x6E, 0x3D, 0x22, 0x32, 0x30, 0x32, 0x32, 0x2D, 0x30, 0x34, 0x2D,\n     0x32, 0x39, 0x54, 0x31, 0x35, 0x3A, 0x32, 0x36, 0x3A, 0x35, 0x34, 0x22,\n     0x2F, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x72, 0x64, 0x66,\n     0x3A, 0x53, 0x65, 0x71, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x78,\n     0x6D, 0x70, 0x4D, 0x4D, 0x3A, 0x48, 0x69, 0x73, 0x74, 0x6F, 0x72, 0x79,\n     0x3E, 0x0A, 0x20, 0x20, 0x20, 0x3C, 0x78, 0x6D, 0x70, 0x4D, 0x4D, 0x3A,\n     0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6F, 0x6D, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x52, 0x65, 0x66, 0x3A, 0x64, 0x6F,\n     0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x49, 0x44, 0x3D, 0x22, 0x36, 0x38,\n     0x38, 0x45, 0x45, 0x41, 0x46, 0x37, 0x30, 0x30, 0x30, 0x37, 0x44, 0x31,\n     0x35, 0x41, 0x41, 0x38, 0x30, 0x45, 0x37, 0x45, 0x42, 0x30, 0x33, 0x39,\n     0x36, 0x41, 0x39, 0x41, 0x44, 0x43, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20,\n     0x73, 0x74, 0x52, 0x65, 0x66, 0x3A, 0x69, 0x6E, 0x73, 0x74, 0x61, 0x6E,\n     0x63, 0x65, 0x49, 0x44, 0x3D, 0x22, 0x78, 0x6D, 0x70, 0x2E, 0x69, 0x69,\n     0x64, 0x3A, 0x36, 0x66, 0x32, 0x32, 0x36, 0x33, 0x34, 0x33, 0x2D, 0x39,\n     0x62, 0x35, 0x65, 0x2D, 0x37, 0x36, 0x34, 0x36, 0x2D, 0x61, 0x32, 0x30,\n     0x62, 0x2D, 0x35, 0x64, 0x31, 0x39, 0x62, 0x66, 0x61, 0x30, 0x30, 0x31,\n     0x66, 0x33, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x52, 0x65,\n     0x66, 0x3A, 0x6F, 0x72, 0x69, 0x67, 0x69, 0x6E, 0x61, 0x6C, 0x44, 0x6F,\n     0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x49, 0x44, 0x3D, 0x22, 0x36, 0x38,\n     0x38, 0x45, 0x45, 0x41, 0x46, 0x37, 0x30, 0x30, 0x30, 0x37, 0x44, 0x31,\n     0x35, 0x41, 0x41, 0x38, 0x30, 0x45, 0x37, 0x45, 0x42, 0x30, 0x33, 0x39,\n     0x36, 0x41, 0x39, 0x41, 0x44, 0x43, 0x22, 0x2F, 0x3E, 0x0A, 0x20, 0x20,\n     0x20, 0x3C, 0x74, 0x69, 0x66, 0x66, 0x3A, 0x42, 0x69, 0x74, 0x73, 0x50,\n     0x65, 0x72, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x3E, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x53, 0x65, 0x71, 0x3E, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69,\n     0x3E, 0x38, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x3E, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69,\n     0x3E, 0x38, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x3E, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69,\n     0x3E, 0x38, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x6C, 0x69, 0x3E, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x53, 0x65,\n     0x71, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x74, 0x69, 0x66, 0x66,\n     0x3A, 0x42, 0x69, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x61, 0x6D, 0x70,\n     0x6C, 0x65, 0x3E, 0x0A, 0x20, 0x20, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A,\n     0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x3E,\n     0x0A, 0x20, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x52, 0x44, 0x46, 0x3E,\n     0x0A, 0x3C, 0x2F, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61,\n     0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n     0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A, 0x3C, 0x3F,\n     0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x65, 0x6E, 0x64, 0x3D,\n     0x22, 0x77, 0x22, 0x3F, 0x3E, 0x4C, 0x13, 0xA6, 0xFF, 0x00, 0x00, 0x16,\n     0x74, 0x7A, 0x54, 0x58, 0x74, 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6F,\n     0x66, 0x69, 0x6C, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x78,\n     0x69, 0x66, 0x00, 0x00, 0x78, 0xDA, 0xA5, 0x9A, 0x69, 0x96, 0x1B, 0x39,\n     0x92, 0x84, 0xFF, 0xE3, 0x14, 0x73, 0x84, 0xC0, 0x0E, 0x1C, 0x07, 0xEB,\n     0x7B, 0x73, 0x83, 0x39, 0xFE, 0x7C, 0xE6, 0xC1, 0xCC, 0x2A, 0xA9, 0xAA,\n     0xBA, 0xA5, 0x6E, 0xA5, 0x92, 0x64, 0x92, 0x11, 0x58, 0x7C, 0x31, 0x33,\n     0x77, 0xD0, 0x9D, 0xFF, 0xFB, 0xDF, 0xEB, 0xFE, 0x87, 0x7F, 0xB9, 0xFB,\n     0xE6, 0x52, 0xAE, 0xAD, 0xF4, 0x52, 0x1E, 0xFE, 0xA5, 0x9E, 0x7A, 0x18,\n     0xBC, 0x68, 0xCF, 0xFB, 0x6F, 0xD8, 0xA3, 0x7F, 0x92, 0x3D, 0xDA, 0xBF,\n     0xF4, 0xF9, 0x88, 0xBF, 0x7F, 0x78, 0xDF, 0x7D, 0x7F, 0x10, 0x78, 0x2B,\n     0xF2, 0x1C, 0xDF, 0x3F, 0x5B, 0xF9, 0x5C, 0xFF, 0xF5, 0xBE, 0xFF, 0x1E,\n     0xE0, 0x7D, 0x1A, 0xBC, 0xCA, 0x7F, 0x1A, 0xA8, 0xAD, 0xCF, 0x07, 0xF3,\n     0xC7, 0x0F, 0xFA, 0x67, 0x86, 0xD0, 0x7E, 0x1A, 0x28, 0xBC, 0x4F, 0x51,\n     0x2B, 0xD2, 0xEB, 0xFD, 0x19, 0xA8, 0x7F, 0x06, 0x8A, 0xE1, 0xFD, 0xC0,\n     0x7F, 0x06, 0x18, 0xEF, 0xB6, 0x9E, 0xD2, 0x5B, 0xFD, 0xF3, 0x16, 0xE6,\n     0x79, 0x9F, 0xF7, 0xD7, 0x4E, 0xDA, 0xFB, 0xEB, 0xF4, 0x10, 0xD6, 0xE7,\n     0xB2, 0xCF, 0xC5, 0x3F, 0xFF, 0x9D, 0x2A, 0xD6, 0xDB, 0x99, 0x79, 0x62,\n     0x08, 0x27, 0xFA, 0xF8, 0xF0, 0x18, 0xE3, 0x67, 0x01, 0x51, 0xBF, 0xC1,\n     0xC5, 0xC1, 0x8B, 0xC4, 0x63, 0x88, 0x9D, 0x0B, 0x7D, 0xCC, 0xF6, 0xBA,\n     0xF0, 0x98, 0xE2, 0xD7, 0x56, 0x31, 0xC8, 0xDF, 0xD9, 0xE9, 0xFB, 0x5F,\n     0x67, 0x45, 0xF7, 0x7C, 0x5C, 0xF1, 0xD7, 0x8B, 0x7E, 0xF0, 0xCA, 0xF7,\n     0x2B, 0xFF, 0xF7, 0xEF, 0xBB, 0x9F, 0xBD, 0x95, 0xC2, 0xE7, 0x92, 0xF8,\n     0x93, 0x91, 0xCB, 0xF7, 0xF3, 0xDF, 0xBE, 0xEF, 0x7C, 0xFE, 0xE9, 0x83,\n     0xF8, 0x3D, 0x4F, 0xF8, 0x21, 0x7E, 0xDA, 0xE7, 0x55, 0xF8, 0xF1, 0xFD,\n     0xFD, 0xF8, 0xFC, 0xAE, 0xE8, 0x27, 0xEB, 0xEB, 0xF7, 0xDE, 0xDD, 0xAE,\n     0xED, 0x99, 0x5D, 0x8C, 0x54, 0x30, 0x75, 0xF9, 0x6C, 0xEA, 0x6B, 0x2B,\n     0xF6, 0x8A, 0xEB, 0x26, 0x53, 0x68, 0xEA, 0xE6, 0x58, 0x5A, 0x79, 0x2A,\n     0xBF, 0x99, 0x21, 0xAA, 0xFD, 0x74, 0x7E, 0x1A, 0x51, 0xBD, 0x08, 0x85,\n     0xFD, 0xAC, 0x67, 0xF2, 0xB3, 0x7C, 0xF7, 0x01, 0x77, 0x5D, 0x9F, 0xFC,\n     0xF6, 0xC3, 0x5F, 0x7F, 0xEC, 0x79, 0xF9, 0xC5, 0x12, 0x53, 0x38, 0x2E,\n     0x54, 0x5E, 0x84, 0xB0, 0x42, 0xB4, 0x37, 0x5B, 0xAC, 0xA1, 0x87, 0x15,\n     0xE5, 0xBF, 0xA4, 0x1F, 0x7F, 0x43, 0x8D, 0x3D, 0xEE, 0xD8, 0xF0, 0xE5,\n     0x32, 0xB7, 0xA7, 0x18, 0xBE, 0xD7, 0xE2, 0x6D, 0xDA, 0xFE, 0x2C, 0x67,\n     0xB3, 0x35, 0x66, 0xDE, 0x9E, 0x4B, 0x83, 0x67, 0x30, 0xAF, 0xB8, 0xF8,\n     0xDD, 0x1F, 0xF7, 0xBB, 0x37, 0xDC, 0xAB, 0x54, 0xF0, 0xFE, 0x69, 0xDF,\n     0xB6, 0x62, 0x5D, 0x21, 0xC8, 0xD8, 0x2C, 0x43, 0x9E, 0xD3, 0x23, 0x97,\n     0xE1, 0x11, 0x7F, 0x3F, 0x46, 0xCD, 0x66, 0xE0, 0xAF, 0x9F, 0x9F, 0xFF,\n     0xC9, 0xAF, 0x11, 0x0F, 0x66, 0x59, 0x59, 0x29, 0xD2, 0x31, 0xEC, 0x7C,\n     0x87, 0x98, 0xD9, 0xFF, 0x81, 0x04, 0xD1, 0x1C, 0x1D, 0xB9, 0x30, 0xF3,\n     0xFC, 0xE6, 0xA0, 0xAF, 0xFB, 0x33, 0x00, 0x26, 0x62, 0xEA, 0xCC, 0x62,\n     0x7C, 0xC4, 0x03, 0x78, 0x8D, 0xAC, 0xF0, 0xC5, 0x3F, 0x35, 0x84, 0xEA,\n     0x3D, 0x86, 0x6C, 0x38, 0x68, 0xB0, 0xF4, 0x10, 0x53, 0x98, 0x78, 0xC0,\n     0xE7, 0x1C, 0x36, 0x8B, 0x0C, 0x29, 0xC6, 0x82, 0x6F, 0x5A, 0xD0, 0xD4,\n     0xDC, 0x52, 0xBD, 0x5D, 0x1A, 0x72, 0xE0, 0x6D, 0xC7, 0xFB, 0x80, 0x99,\n     0xF2, 0x8A, 0xFC, 0xAA, 0xF8, 0xA6, 0xC7, 0x81, 0xB3, 0x52, 0xCA, 0xC4,\n     0x4F, 0x4D, 0x8D, 0x18, 0x1A, 0x39, 0xE6, 0x94, 0x73, 0x2E, 0xB9, 0xE6,\n     0x96, 0x7B, 0x1E, 0x25, 0x96, 0x54, 0x72, 0x29, 0xA5, 0x16, 0x81, 0xE2,\n     0xA8, 0xB1, 0x26, 0x57, 0x73, 0x2D, 0xB5, 0xD6, 0x56, 0x7B, 0x1D, 0x2D,\n     0xB6, 0xD4, 0x72, 0x2B, 0xAD, 0xB6, 0xD6, 0x7A, 0x1B, 0x3D, 0xF4, 0x08,\n     0x68, 0xE6, 0x5E, 0x7A, 0xED, 0xAD, 0xF7, 0x3E, 0x06, 0x73, 0x0E, 0x46,\n     0x1E, 0xDC, 0x3D, 0xB8, 0x60, 0x8C, 0x19, 0x66, 0x9C, 0x69, 0x66, 0x37,\n     0xCB, 0xAC, 0xB3, 0xCD, 0x3E, 0xC7, 0x22, 0x7C, 0x56, 0x5A, 0x79, 0x95,\n     0x55, 0x57, 0x5B, 0x7D, 0x8D, 0x1D, 0x76, 0xDC, 0xE0, 0xC7, 0x2E, 0xBB,\n     0xEE, 0xB6, 0xFB, 0x1E, 0xC7, 0x1F, 0x42, 0xE9, 0xA4, 0x93, 0x4F, 0x39,\n     0xF5, 0xB4, 0xD3, 0xCF, 0xB8, 0x84, 0xDA, 0x8D, 0xEE, 0xA6, 0x9B, 0x6F,\n     0xB9, 0xF5, 0xB6, 0xDB, 0xEF, 0xF8, 0xF6, 0xDA, 0xC7, 0xAD, 0x7F, 0xF9,\n     0xF9, 0x0D, 0xAF, 0xF9, 0x8F, 0xD7, 0x82, 0x79, 0x4A, 0x17, 0xD6, 0x6F,\n     0xAF, 0xF1, 0x6E, 0xAD, 0x5F, 0x43, 0x78, 0xC1, 0x49, 0x96, 0xCF, 0x70,\n     0x58, 0x70, 0xC9, 0xE3, 0xF1, 0x2A, 0x17, 0x10, 0xD0, 0x41, 0x3E, 0x7B,\n     0x9A, 0x4F, 0x29, 0xC8, 0x73, 0xF2, 0xD9, 0xD3, 0x03, 0x59, 0x91, 0x03,\n     0x8B, 0xCC, 0xF2, 0xD9, 0xF6, 0xF2, 0x18, 0x1E, 0x4C, 0xC7, 0x87, 0x7C,\n     0xFD, 0x97, 0xEF, 0x5C, 0x78, 0x3D, 0x2A, 0xCF, 0xFD, 0x57, 0x7E, 0x73,\n     0x35, 0xFD, 0xE0, 0xB7, 0xF0, 0x9F, 0x7A, 0xCE, 0xC9, 0x75, 0xBF, 0xE9,\n     0xB9, 0xBF, 0xFA, 0xED, 0xEF, 0xBC, 0xB6, 0x45, 0x43, 0xCB, 0x3C, 0xF6,\n     0x66, 0xA1, 0x8C, 0xFA, 0x44, 0xB2, 0x8F, 0x6B, 0x46, 0x68, 0xFC, 0x87,\n     0xAB, 0xFE, 0xFA, 0xEC, 0xFE, 0xE9, 0x83, 0x7F, 0x7C, 0x3E, 0x21, 0xEC,\n     0x79, 0x02, 0x49, 0x37, 0x62, 0xAD, 0xF8, 0x7A, 0xDC, 0x72, 0xF6, 0x72,\n     0xBC, 0x9C, 0xAB, 0xD4, 0x7B, 0x4B, 0x1E, 0xE9, 0xA4, 0x5E, 0x4E, 0x4C,\n     0x9D, 0x60, 0xAD, 0x23, 0xCC, 0x7E, 0xEF, 0xB3, 0x6A, 0xD3, 0xD3, 0x83,\n     0x8F, 0xE2, 0x9D, 0x99, 0x55, 0xF9, 0xBB, 0xFB, 0x5D, 0xEC, 0xF2, 0xDE,\n     0xB0, 0x6B, 0x3D, 0x67, 0xEF, 0x39, 0xB7, 0x6B, 0x25, 0xF2, 0xB2, 0x26,\n     0x9F, 0x16, 0xBF, 0x99, 0xFF, 0x62, 0xDB, 0xDF, 0x7F, 0x76, 0xBF, 0x7A,\n     0x61, 0xC6, 0xA7, 0xAB, 0x4C, 0x2D, 0x07, 0x53, 0xDF, 0x73, 0xE2, 0xDA,\n     0x97, 0xBD, 0x55, 0xD0, 0x7B, 0xAF, 0x12, 0x9D, 0x3F, 0xAB, 0xEE, 0xD2,\n     0x63, 0x2D, 0x0F, 0x61, 0xD7, 0xF0, 0x40, 0xB9, 0x3E, 0xDC, 0xB6, 0xEE,\n     0x9D, 0xA9, 0x9E, 0x35, 0xEF, 0x69, 0xFE, 0xE4, 0xDB, 0x2B, 0xC1, 0x7F,\n     0x70, 0x30, 0x08, 0xBE, 0xEA, 0x9D, 0xF1, 0xEC, 0x9C, 0x07, 0x9B, 0xE5,\n     0xB7, 0xF2, 0x29, 0x4C, 0xDB, 0x78, 0x97, 0xC8, 0xC2, 0x6D, 0x5C, 0x72,\n     0xE2, 0x9E, 0x7B, 0x8C, 0xB8, 0x61, 0x21, 0xBC, 0x4C, 0xCC, 0xDD, 0xBA,\n     0x72, 0x60, 0x90, 0x09, 0xFB, 0x14, 0x2D, 0x65, 0xD6, 0x73, 0x6F, 0x9C,\n     0xB7, 0x4D, 0xDF, 0x08, 0x13, 0xB9, 0x7C, 0x9C, 0x82, 0xFB, 0xC7, 0xDD,\n     0xFB, 0x76, 0x7E, 0xB1, 0xF0, 0x1D, 0xBB, 0xC6, 0x5B, 0xDA, 0x26, 0x44,\n     0xEE, 0x89, 0x8C, 0xE5, 0xCF, 0xCD, 0x93, 0x91, 0x6A, 0x3A, 0x61, 0x3E,\n     0xC4, 0xE7, 0x29, 0x75, 0xF8, 0x31, 0xC8, 0x97, 0xBB, 0x82, 0xEE, 0x5B,\n     0xB9, 0x8E, 0x52, 0x5C, 0xBB, 0x83, 0x95, 0x96, 0xC1, 0x5B, 0x77, 0xB6,\n     0x1B, 0x26, 0x31, 0x38, 0xEF, 0xAA, 0x73, 0xB2, 0x3E, 0x2D, 0x83, 0x89,\n     0xE2, 0x83, 0xE7, 0x88, 0xCE, 0xEB, 0xFB, 0xDA, 0xB5, 0xEC, 0xF3, 0x8C,\n     0xBA, 0x59, 0x1F, 0x86, 0xF2, 0xDC, 0x72, 0xE5, 0x55, 0x57, 0xCA, 0x60,\n     0xF8, 0x50, 0xD7, 0x2E, 0xA1, 0x24, 0xE2, 0xBC, 0xEF, 0x82, 0x69, 0xB1,\n     0x46, 0xBD, 0x27, 0x10, 0xA1, 0xE4, 0xDA, 0xC4, 0x52, 0x37, 0x05, 0x4C,\n     0x50, 0x71, 0x7A, 0xFC, 0xC3, 0xED, 0xED, 0x16, 0x46, 0x9A, 0x93, 0x8D,\n     0xB8, 0x92, 0x59, 0xE1, 0xD8, 0xC4, 0xEE, 0x11, 0x8B, 0x9C, 0x5D, 0x34,\n     0x7E, 0xEA, 0x32, 0xE1, 0x9E, 0x75, 0x11, 0x2D, 0xBD, 0x54, 0x8C, 0xB5,\n     0x76, 0x9F, 0x18, 0x81, 0x85, 0x6E, 0xAE, 0xDA, 0x61, 0x96, 0x5B, 0xBA,\n     0x9F, 0xBB, 0xF5, 0x93, 0x4A, 0x9D, 0x2E, 0x66, 0x73, 0x6E, 0x21, 0x53,\n     0xCD, 0xDB, 0xCF, 0xF3, 0x9F, 0x3D, 0xC3, 0x6B, 0xB9, 0x82, 0x44, 0x84,\n     0x3A, 0x88, 0x49, 0x98, 0xE0, 0xFB, 0xB9, 0x0E, 0xDB, 0xEA, 0x27, 0x6A,\n     0x6D, 0x13, 0xC3, 0xC2, 0xF4, 0xE9, 0x14, 0xB8, 0xFB, 0xCE, 0xC0, 0xE2,\n     0xF8, 0x78, 0x1D, 0x3C, 0x77, 0x4E, 0xAE, 0xFD, 0xAC, 0x4C, 0x7C, 0x01,\n     0x23, 0xB2, 0x30, 0xB6, 0x35, 0x83, 0x8D, 0x9D, 0x7B, 0x3A, 0x58, 0xF3,\n     0x99, 0x38, 0xF6, 0x8E, 0x19, 0x37, 0xC6, 0x4E, 0x9B, 0x4F, 0x19, 0x61,\n     0xD9, 0x35, 0xA9, 0x10, 0x86, 0x8B, 0x2D, 0xCA, 0x52, 0xE6, 0xD6, 0x7E,\n     0x49, 0x8E, 0xE9, 0xF2, 0x69, 0x3D, 0x9F, 0x67, 0xDF, 0x32, 0x19, 0xEE,\n     0xA9, 0x87, 0x10, 0x03, 0x43, 0xE7, 0x9C, 0xB5, 0x7A, 0x8C, 0x18, 0xDA,\n     0xDA, 0x63, 0x2F, 0xC0, 0xEF, 0xE6, 0x54, 0x27, 0xA3, 0xB6, 0x51, 0x1B,\n     0x71, 0x9D, 0xBB, 0x6C, 0x78, 0xCB, 0x63, 0xC3, 0x07, 0xC7, 0x82, 0x31,\n     0x68, 0xC4, 0xD6, 0x79, 0x9C, 0xC3, 0xAA, 0xCB, 0xEB, 0xF3, 0xD3, 0x00,\n     0x26, 0xCF, 0x0D, 0x4B, 0x9E, 0xC2, 0x81, 0x19, 0x53, 0x1F, 0xDD, 0xD4,\n     0x5F, 0x77, 0xE0, 0x7E, 0xE6, 0x1E, 0x99, 0xD9, 0xB4, 0x73, 0x07, 0xD6,\n     0x9D, 0x49, 0xBA, 0x96, 0xB8, 0xB5, 0x60, 0x7C, 0x31, 0x49, 0x01, 0x36,\n     0xB9, 0xDA, 0xC4, 0x1C, 0x57, 0x71, 0x91, 0xC9, 0xB1, 0x75, 0xBB, 0xD9,\n     0xEC, 0x09, 0x98, 0x0F, 0xDB, 0xCC, 0x53, 0x88, 0x67, 0x42, 0x37, 0xEC,\n     0xC1, 0xDF, 0x09, 0xA6, 0x25, 0x9B, 0x22, 0x3F, 0x05, 0xE3, 0x11, 0xEE,\n     0x63, 0x97, 0x05, 0x90, 0xB4, 0x65, 0x1B, 0xCB, 0xCF, 0xE6, 0x33, 0x62,\n     0x6E, 0xB1, 0x56, 0x25, 0x99, 0x5D, 0xD0, 0xD3, 0xAC, 0x7E, 0x8A, 0xCF,\n     0x8A, 0x22, 0x3F, 0x2D, 0x20, 0xC8, 0x4D, 0x5F, 0xF6, 0x26, 0xEC, 0x0B,\n     0x19, 0xAC, 0x3B, 0xF7, 0xC6, 0x96, 0x59, 0x73, 0xDF, 0xA7, 0x6D, 0x66,\n     0x26, 0x19, 0x49, 0x1A, 0xE2, 0x8A, 0x45, 0xDB, 0xFE, 0x17, 0xEB, 0x26,\n     0x8F, 0x8E, 0x42, 0x9D, 0xCD, 0xEE, 0x30, 0x66, 0x4A, 0xD3, 0x11, 0x6A,\n     0x95, 0x5C, 0x2D, 0x13, 0x29, 0xEE, 0x49, 0xE9, 0x3E, 0x6B, 0xD7, 0x43,\n     0x3C, 0x97, 0x7C, 0x88, 0x8D, 0x54, 0x99, 0xC3, 0x57, 0xD9, 0xDC, 0xEF,\n     0xEA, 0x4F, 0x65, 0x0C, 0xF2, 0x12, 0x9F, 0x6C, 0x98, 0x66, 0xEB, 0xF6,\n     0xA6, 0xDB, 0x5D, 0x38, 0x5A, 0x0A, 0x16, 0xFF, 0xAC, 0xFD, 0xE6, 0xB1,\n     0x59, 0x79, 0x23, 0x98, 0xC8, 0x47, 0x25, 0xD3, 0xD9, 0xA4, 0xDB, 0x10,\n     0xEE, 0xAF, 0xDB, 0xAA, 0x05, 0x1C, 0x6B, 0xD2, 0x22, 0x6F, 0xCA, 0x17,\n     0x3D, 0xD8, 0x15, 0x0B, 0x04, 0x24, 0xE1, 0x50, 0x85, 0x32, 0xE4, 0x0D,\n     0x66, 0x9C, 0x35, 0x2F, 0x6E, 0xA0, 0xFA, 0x2A, 0x77, 0x5B, 0xE2, 0x72,\n     0x3F, 0x1B, 0xDD, 0x7D, 0x4C, 0x08, 0xE8, 0xCC, 0x16, 0x81, 0xE6, 0x03,\n     0xB7, 0xEE, 0x9B, 0x5E, 0x30, 0x1F, 0x0D, 0x67, 0xB9, 0xFF, 0x0C, 0x5C,\n     0xE1, 0xC0, 0xBA, 0x4A, 0x20, 0x18, 0x8F, 0x0C, 0x8A, 0xAF, 0x9D, 0xA5,\n     0x41, 0x3A, 0x73, 0xB5, 0x43, 0x0A, 0xA0, 0x60, 0x15, 0xEF, 0xF9, 0xB0,\n     0x76, 0xDF, 0x48, 0xC6, 0x3B, 0x71, 0xCF, 0xD1, 0xDA, 0xCF, 0x8E, 0x82,\n     0xCF, 0x25, 0xCF, 0x6F, 0xC8, 0xA4, 0x8E, 0x0E, 0x61, 0xCF, 0x04, 0x3D,\n     0x27, 0xC8, 0xCE, 0xDD, 0x05, 0x1E, 0xAD, 0x2E, 0xEC, 0xD9, 0xC9, 0x87,\n     0x33, 0x70, 0x11, 0xEF, 0x00, 0x84, 0x90, 0x0C, 0x41, 0x37, 0xB1, 0x6D,\n     0x98, 0xE1, 0x10, 0x91, 0xE0, 0x05, 0x11, 0x06, 0x1E, 0x76, 0x74, 0x42,\n     0xEC, 0x04, 0x92, 0x5F, 0x8D, 0x50, 0x5A, 0x0B, 0xB7, 0xBB, 0xB3, 0x65,\n     0xD0, 0x91, 0x98, 0x39, 0x03, 0x35, 0xD8, 0xA7, 0x1B, 0x4F, 0x11, 0x0F,\n     0x4A, 0x10, 0xB6, 0x52, 0x78, 0x0F, 0x0C, 0x61, 0xA6, 0x2C, 0xA2, 0xEF,\n     0x45, 0x98, 0x54, 0xC9, 0xEB, 0xC4, 0x4C, 0x19, 0xC9, 0xB0, 0x5B, 0x41,\n     0x44, 0xF4, 0x85, 0x7D, 0x17, 0x00, 0x39, 0x77, 0x9D, 0x95, 0xC9, 0x51,\n     0xF1, 0x03, 0x0A, 0x04, 0xDD, 0x8B, 0x1C, 0x09, 0x80, 0x3F, 0xF8, 0x70,\n     0x10, 0xE1, 0x17, 0x00, 0xCC, 0xB8, 0xF4, 0x12, 0x1B, 0xF9, 0x66, 0x30,\n     0x38, 0x80, 0xD9, 0x73, 0x2B, 0x2A, 0xB7, 0x23, 0xCD, 0x08, 0x23, 0xC2,\n     0x78, 0x97, 0x51, 0x66, 0x82, 0xF5, 0xEF, 0x61, 0xBC, 0x46, 0x44, 0x27,\n     0x8B, 0x54, 0xFC, 0x4F, 0x7A, 0x96, 0xBE, 0x7C, 0xDC, 0x50, 0x7D, 0x7B,\n     0x10, 0x09, 0x51, 0x22, 0xAD, 0x58, 0xFA, 0x14, 0x92, 0xFE, 0xCE, 0x73,\n     0x1D, 0xF4, 0x89, 0x46, 0x91, 0xC9, 0xF3, 0x20, 0xC1, 0x9F, 0x0E, 0xD6,\n     0x5C, 0xD1, 0x51, 0x89, 0x71, 0x5B, 0x5A, 0x8D, 0xE5, 0x09, 0x05, 0x59,\n     0x71, 0x28, 0xCA, 0xA8, 0x36, 0x24, 0x2A, 0xF0, 0x44, 0xAC, 0x99, 0x28,\n     0x22, 0x23, 0x4F, 0x6C, 0xCB, 0x31, 0xFD, 0x19, 0xBD, 0x65, 0xE1, 0x67,\n     0x1B, 0xC8, 0x28, 0x42, 0x0D, 0x84, 0x22, 0x67, 0x48, 0xC1, 0xD4, 0x02,\n     0x0B, 0x07, 0x0D, 0x9E, 0xB1, 0x30, 0x2E, 0x74, 0x6F, 0x33, 0xE0, 0x4C,\n     0x12, 0x25, 0x2B, 0xD4, 0x59, 0xA5, 0xAF, 0x06, 0xFE, 0x46, 0x7C, 0xD4,\n     0x45, 0xEF, 0x47, 0xCF, 0xAE, 0x84, 0x3E, 0x46, 0xED, 0x0D, 0x34, 0x30,\n     0xE9, 0x30, 0xA6, 0xFF, 0x15, 0xB4, 0x75, 0x7F, 0xF7, 0x81, 0x3F, 0x50,\n     0x82, 0x47, 0x80, 0x41, 0xC8, 0x12, 0x71, 0x9A, 0x91, 0x7D, 0x2A, 0x91,\n     0x4B, 0x27, 0x91, 0x53, 0xEA, 0xA4, 0xFA, 0x52, 0x36, 0xAC, 0x80, 0x93,\n     0xE2, 0x09, 0x8B, 0x5C, 0x83, 0x09, 0x89, 0x46, 0xCC, 0xAD, 0x28, 0xE4,\n     0xE2, 0x42, 0x6A, 0x05, 0x84, 0x57, 0x27, 0x98, 0x20, 0xF6, 0xB9, 0xCF,\n     0x44, 0x4C, 0xD5, 0x05, 0xC8, 0x3E, 0xBB, 0xAD, 0x49, 0xE5, 0x8C, 0x89,\n     0x88, 0xED, 0x08, 0x50, 0x62, 0xB4, 0xB1, 0x84, 0x07, 0xCE, 0x13, 0xE9,\n     0xA3, 0x34, 0x45, 0xF5, 0x25, 0xEC, 0xE7, 0x1B, 0x99, 0x5C, 0x58, 0xD8,\n     0xE1, 0xCE, 0xBC, 0xB3, 0xAF, 0xC2, 0x80, 0x2D, 0xEE, 0x51, 0xD3, 0x94,\n     0x0C, 0x98, 0x37, 0xE8, 0x9D, 0xF3, 0x71, 0x51, 0x4C, 0x6B, 0xC2, 0xFD,\n     0x48, 0x05, 0x3E, 0x89, 0x52, 0x39, 0x24, 0x7A, 0x67, 0x8A, 0x2A, 0x50,\n     0x43, 0x31, 0x0A, 0x0E, 0x06, 0xE5, 0x49, 0x46, 0x8B, 0x02, 0x86, 0x75,\n     0x16, 0x34, 0x6B, 0x20, 0x1E, 0x3B, 0x10, 0x33, 0xFA, 0xF4, 0x89, 0x92,\n     0x0F, 0xC8, 0x99, 0xA0, 0x65, 0x73, 0x98, 0x14, 0x42, 0x69, 0x9B, 0x8C,\n     0x00, 0x58, 0x76, 0x26, 0x68, 0x4E, 0xDE, 0x33, 0xF7, 0x1D, 0x60, 0x76,\n     0x60, 0x5E, 0xA3, 0xED, 0x92, 0x10, 0x06, 0x31, 0x8D, 0x85, 0x54, 0x26,\n     0x59, 0x48, 0x4D, 0xE1, 0x64, 0xAA, 0x1A, 0xFE, 0x20, 0x6B, 0x0F, 0xF2,\n     0x58, 0xE4, 0x09, 0xCE, 0x66, 0x60, 0x87, 0xCC, 0xF1, 0x6C, 0x4C, 0x09,\n     0x80, 0x24, 0x5E, 0xC8, 0xE9, 0xD5, 0x81, 0x7E, 0xA1, 0x09, 0xD4, 0x34,\n     0x94, 0x92, 0xC4, 0x6F, 0x43, 0x76, 0x82, 0x6B, 0x08, 0x3C, 0x66, 0x5D,\n     0x86, 0xA9, 0xA4, 0x82, 0x53, 0x78, 0x98, 0x9C, 0x21, 0x3F, 0x4A, 0xCB,\n     0xC0, 0x0D, 0xAC, 0x8B, 0xB7, 0x06, 0x57, 0x41, 0xDA, 0x02, 0xF9, 0x7B,\n     0xEE, 0x24, 0xDF, 0x22, 0xF1, 0xBC, 0xA2, 0x45, 0x2A, 0xFE, 0xC1, 0x04,\n     0x46, 0xD1, 0x18, 0x2E, 0xFC, 0xA3, 0xFB, 0x7F, 0xF5, 0xD9, 0x83, 0x45,\n     0x04, 0x6F, 0xB2, 0x80, 0x9C, 0x16, 0x97, 0x28, 0xF6, 0x73, 0xD9, 0xB1,\n     0xE6, 0x05, 0x4F, 0xB5, 0xC4, 0x7D, 0x4D, 0xED, 0x3C, 0x47, 0x0C, 0x8E,\n     0x8C, 0x88, 0xE4, 0x29, 0x2A, 0x64, 0x2D, 0x3F, 0xE4, 0xE3, 0x31, 0xC9,\n     0x09, 0x2D, 0xBC, 0xE4, 0x4C, 0xC8, 0x45, 0x87, 0x80, 0x47, 0xA8, 0x36,\n     0x20, 0xC8, 0x4B, 0xE6, 0x6F, 0x89, 0xC0, 0x43, 0x54, 0x35, 0x74, 0x5A,\n     0x6A, 0xE2, 0x38, 0x4A, 0x0B, 0x70, 0xF6, 0x08, 0x9D, 0x1A, 0xDB, 0x46,\n     0xEF, 0x27, 0x54, 0x16, 0xA1, 0xB3, 0x04, 0xE9, 0x5B, 0xDC, 0x53, 0x46,\n     0x76, 0x0B, 0x7F, 0x80, 0x94, 0x60, 0x13, 0x71, 0x32, 0xA9, 0x6B, 0x50,\n     0x43, 0x08, 0xC8, 0x35, 0x05, 0x24, 0x97, 0xEC, 0x03, 0x6D, 0x8A, 0x34,\n     0x43, 0x57, 0xC6, 0x12, 0xAD, 0xF5, 0x41, 0x25, 0x0D, 0x11, 0xFA, 0x64,\n     0x4A, 0x72, 0x6D, 0x50, 0x9D, 0x24, 0x4A, 0x08, 0x89, 0xEE, 0x7D, 0x70,\n     0x0D, 0x9E, 0x00, 0xB6, 0xA4, 0xCC, 0xAA, 0x94, 0x59, 0x18, 0x2B, 0x37,\n     0xA8, 0x1A, 0x80, 0x6C, 0x14, 0xAE, 0x13, 0x4E, 0x91, 0x12, 0x62, 0x15,\n     0xA2, 0x0A, 0xA4, 0x28, 0x8B, 0x54, 0x9C, 0x2E, 0x49, 0xB4, 0x7D, 0x5D,\n     0xAD, 0x50, 0xB1, 0x65, 0x13, 0x6A, 0x0F, 0x20, 0x82, 0x27, 0x1E, 0x85,\n     0x26, 0x21, 0x07, 0x88, 0x3D, 0x13, 0x30, 0xD9, 0x12, 0x04, 0x08, 0x04,\n     0x89, 0x95, 0x2C, 0x02, 0x8A, 0xAC, 0x70, 0x21, 0xCC, 0x72, 0x22, 0xC0,\n     0xF6, 0x66, 0x7D, 0xA9, 0xB8, 0x88, 0x3E, 0x45, 0xC9, 0xCA, 0x17, 0x88,\n     0x32, 0x25, 0x16, 0x3B, 0x09, 0x02, 0x57, 0xD9, 0x9D, 0x82, 0x08, 0x77,\n     0x2B, 0x32, 0x26, 0x05, 0xFA, 0x16, 0x74, 0xA0, 0x82, 0x87, 0xC4, 0x1C,\n     0x3E, 0x80, 0x68, 0x8F, 0x84, 0x1C, 0x8A, 0xB8, 0x3A, 0xC3, 0xC7, 0x4D,\n     0x2D, 0x83, 0x26, 0xF6, 0x3B, 0x75, 0x22, 0x3E, 0xC4, 0x71, 0x8C, 0xB0,\n     0x26, 0x45, 0x45, 0xFE, 0xC5, 0x42, 0xC5, 0xFD, 0x8B, 0x0B, 0xD8, 0xDD,\n     0xCC, 0x29, 0x4A, 0xEA, 0x22, 0x18, 0x00, 0x4B, 0xD4, 0x0E, 0xFE, 0xBF,\n     0xE2, 0x7F, 0x56, 0x79, 0x8C, 0x38, 0x41, 0xB9, 0x28, 0x31, 0xE5, 0x80,\n     0xFB, 0xA8, 0x04, 0x1C, 0x94, 0x65, 0xEB, 0x08, 0xB3, 0xA3, 0x87, 0x65,\n     0x97, 0xF2, 0xFE, 0xCF, 0x78, 0x3E, 0xC1, 0x92, 0x9E, 0x93, 0xC5, 0x12,\n     0x63, 0x50, 0x0C, 0x10, 0x44, 0x60, 0x34, 0x12, 0x1D, 0x28, 0x5D, 0xD8,\n     0x08, 0x7D, 0x2B, 0xED, 0xBE, 0x24, 0xD5, 0x95, 0xAD, 0xA4, 0x26, 0x75,\n     0x07, 0x25, 0xD3, 0x17, 0xA6, 0x90, 0x36, 0x35, 0x3F, 0xD0, 0x34, 0xE2,\n     0x01, 0x20, 0x15, 0xC2, 0x31, 0x30, 0x42, 0x38, 0x41, 0x79, 0x23, 0x2B,\n     0xF7, 0x81, 0x20, 0xA7, 0x3B, 0x8C, 0x77, 0xE0, 0x52, 0x30, 0xAA, 0x11,\n     0xA5, 0x64, 0xDF, 0x7D, 0x4C, 0xF9, 0xE4, 0x84, 0xFA, 0x0F, 0x19, 0xA4,\n     0x9B, 0x1E, 0x4B, 0x2F, 0xEA, 0x48, 0xB1, 0x6E, 0x30, 0x9A, 0xCC, 0x1D,\n     0x72, 0xBA, 0xCD, 0x44, 0x7B, 0x1D, 0x0E, 0x5A, 0xF3, 0x3D, 0x5A, 0x4E,\n     0xCA, 0x41, 0xDA, 0x06, 0x72, 0x27, 0x82, 0x51, 0xB8, 0xB2, 0x09, 0xB5,\n     0xC8, 0xE2, 0xA3, 0xD4, 0x10, 0x0C, 0x78, 0x70, 0xE8, 0xA1, 0xE2, 0xDC,\n     0xA6, 0xBE, 0xBB, 0x59, 0x09, 0x20, 0x48, 0xB9, 0x23, 0xB4, 0x20, 0x7D,\n     0x82, 0xD8, 0x98, 0x5F, 0x21, 0x3E, 0x65, 0x96, 0xB9, 0x45, 0xDE, 0xD4,\n     0x3A, 0xB0, 0x3A, 0xD2, 0x55, 0x71, 0xBD, 0xE3, 0x14, 0x06, 0x01, 0xE1,\n     0x53, 0x50, 0x2A, 0x4D, 0x07, 0x5C, 0x07, 0xB6, 0x03, 0x80, 0x94, 0xE3,\n     0xDE, 0xB4, 0x42, 0x0B, 0x6A, 0x80, 0x03, 0x98, 0x37, 0x4A, 0x17, 0xB8,\n     0x74, 0x06, 0xE2, 0xB2, 0x52, 0x32, 0xE6, 0x7E, 0x3C, 0x35, 0xF7, 0x52,\n     0x49, 0x65, 0xD4, 0x7D, 0x94, 0xD0, 0x6C, 0xBB, 0x3E, 0xAB, 0xD5, 0x6A,\n     0xC0, 0x89, 0x21, 0xDD, 0xE1, 0x4E, 0x74, 0xC4, 0x37, 0xBB, 0x23, 0x80,\n     0xA9, 0xB9, 0x23, 0x29, 0x7E, 0xF6, 0xF0, 0xE1, 0xAD, 0x0B, 0x58, 0xF6,\n     0xBF, 0x2B, 0x0C, 0xDC, 0xBF, 0xC7, 0x9E, 0xDA, 0xBD, 0xDA, 0x1D, 0x1B,\n     0x7D, 0x43, 0xC2, 0xF0, 0x8B, 0x4E, 0x0E, 0x56, 0x3F, 0xEA, 0xE1, 0x28,\n     0xA1, 0x56, 0x81, 0xFB, 0x49, 0xC0, 0x09, 0x08, 0xA3, 0xDA, 0x2B, 0x64,\n     0x06, 0xB6, 0x44, 0x55, 0x9B, 0x70, 0xED, 0xFE, 0x22, 0xA7, 0xAE, 0x3A,\n     0x81, 0xC8, 0x41, 0xA8, 0xAF, 0x4E, 0x9A, 0x52, 0x89, 0x66, 0x16, 0x29,\n     0xBD, 0x48, 0xAD, 0x67, 0x08, 0x7B, 0x8F, 0x43, 0x04, 0xAB, 0x13, 0x81,\n     0xF6, 0x0A, 0xE4, 0xDC, 0x8D, 0xC4, 0x19, 0x1F, 0x7F, 0x0A, 0x44, 0x49,\n     0xA5, 0x1C, 0x59, 0x47, 0x45, 0xF4, 0x01, 0x59, 0x5D, 0x69, 0x6B, 0x42,\n     0x10, 0xEB, 0x48, 0x4E, 0x03, 0x87, 0xC8, 0x4E, 0x8F, 0xB7, 0x29, 0xFC,\n     0x8C, 0x9D, 0xA8, 0xAE, 0x76, 0x17, 0x8A, 0x50, 0x7B, 0x2A, 0x59, 0x25,\n     0x81, 0x8B, 0xC0, 0x12, 0x86, 0xC4, 0xB0, 0xBB, 0x11, 0x7A, 0x30, 0x0A,\n     0x77, 0x33, 0x1D, 0xEF, 0x13, 0x91, 0x6B, 0x16, 0xE0, 0x46, 0xC8, 0x6A,\n     0x50, 0xEB, 0x8D, 0x8A, 0x41, 0xB5, 0x08, 0x71, 0x60, 0x79, 0x14, 0x4F,\n     0x46, 0x75, 0x81, 0x5A, 0xD2, 0x57, 0x06, 0x7F, 0x84, 0x1A, 0x2F, 0xC4,\n     0x3B, 0xD2, 0x56, 0xEA, 0xB8, 0x85, 0x25, 0x68, 0xD6, 0xCA, 0x4E, 0x51,\n     0x3C, 0x1E, 0x84, 0xD6, 0x04, 0x16, 0xC4, 0x7E, 0x4D, 0x8C, 0xEB, 0x7B,\n     0x61, 0x27, 0x68, 0x60, 0x68, 0xF9, 0x7A, 0x55, 0xA3, 0x82, 0xED, 0x38,\n     0x66, 0x13, 0xE7, 0xAA, 0x24, 0x22, 0x48, 0x60, 0x91, 0xFB, 0x4A, 0x7A,\n     0x32, 0x05, 0xD9, 0x92, 0x37, 0x85, 0xC1, 0x76, 0x6F, 0x99, 0x02, 0xA7,\n     0xAB, 0x04, 0x27, 0x8B, 0x88, 0x7E, 0xA0, 0x8C, 0xDB, 0x40, 0xBD, 0x23,\n     0xE0, 0x91, 0xA3, 0x6C, 0xD5, 0x01, 0x29, 0x04, 0xC7, 0x12, 0xC7, 0x37,\n     0xA9, 0x4D, 0x43, 0x1E, 0x6D, 0xC4, 0x44, 0x51, 0x35, 0x57, 0xBB, 0x23,\n     0xE9, 0x2E, 0x55, 0x19, 0x73, 0x5F, 0x14, 0xDB, 0x67, 0x1E, 0xF0, 0xC9,\n     0x17, 0xE8, 0x83, 0xB2, 0xFC, 0x22, 0xDC, 0x64, 0x2F, 0xE4, 0x99, 0x4A,\n     0x70, 0x0F, 0x59, 0xA8, 0x11, 0x50, 0xA4, 0x00, 0x4E, 0xB7, 0xB4, 0x82,\n     0x9A, 0xFB, 0x75, 0xBB, 0x0A, 0xF6, 0x23, 0xA5, 0x08, 0x62, 0x0F, 0x1F,\n     0x17, 0x6E, 0x60, 0xAB, 0x77, 0x59, 0x75, 0xE2, 0xAD, 0x2F, 0x42, 0x11,\n     0x49, 0x7C, 0x32, 0x22, 0x69, 0x97, 0xC1, 0x1C, 0x40, 0xDF, 0xEA, 0x39,\n     0xAE, 0x50, 0x50, 0x53, 0x72, 0x16, 0x92, 0x16, 0x61, 0x81, 0x84, 0xD6,\n     0x00, 0x76, 0x45, 0x14, 0x5F, 0x95, 0x89, 0x0B, 0xC7, 0x20, 0x7E, 0x60,\n     0x2D, 0x54, 0xCA, 0x0B, 0x76, 0x68, 0xEA, 0xF8, 0x8F, 0x30, 0xE8, 0x7E,\n     0xB9, 0xE3, 0x73, 0x9E, 0x79, 0xD4, 0x04, 0x95, 0x9C, 0x12, 0xDA, 0x35,\n     0x24, 0x82, 0x7A, 0x13, 0x83, 0xE2, 0x0C, 0x79, 0xED, 0x40, 0x19, 0x54,\n     0x18, 0x01, 0x14, 0x16, 0x44, 0x21, 0x49, 0x8E, 0x7B, 0xE2, 0xF0, 0xE2,\n     0x32, 0xCB, 0x43, 0xD1, 0x9E, 0xF6, 0x42, 0xE9, 0x26, 0x9A, 0x39, 0x20,\n     0x5B, 0x98, 0x8F, 0x44, 0x7D, 0x0A, 0xA5, 0xB0, 0xD1, 0x66, 0x60, 0xE2,\n     0x1E, 0x29, 0x0B, 0xD8, 0x93, 0xBF, 0x49, 0x6A, 0xD0, 0x02, 0x70, 0x28,\n     0x79, 0x3D, 0xE2, 0x5B, 0xF6, 0xE6, 0x29, 0x39, 0x18, 0x70, 0xA4, 0xED,\n     0x7D, 0x45, 0x2C, 0x09, 0x31, 0xA4, 0x5A, 0x19, 0x97, 0xF8, 0xC0, 0x76,\n     0xBB, 0xE1, 0x70, 0xD6, 0xE1, 0x9A, 0x08, 0x94, 0xEA, 0x8F, 0xF4, 0xEA,\n     0x72, 0x84, 0x81, 0x36, 0x84, 0x19, 0x1F, 0xA1, 0x11, 0x5A, 0xD2, 0xDA,\n     0x56, 0xD3, 0x58, 0x89, 0xB2, 0x73, 0xE1, 0x60, 0x62, 0x0C, 0x33, 0x43,\n     0x50, 0x90, 0x3A, 0xE8, 0x73, 0x40, 0xF6, 0xD1, 0x5D, 0x80, 0x20, 0xA1,\n     0x84, 0x26, 0x25, 0x0F, 0xF4, 0x87, 0x95, 0xAE, 0x07, 0xA0, 0xCB, 0x20,\n     0x0B, 0x28, 0xB9, 0xC8, 0x0A, 0xB2, 0x4B, 0x25, 0x27, 0x21, 0x04, 0x8B,\n     0x0C, 0x04, 0xF1, 0x56, 0x4B, 0x4B, 0x94, 0x29, 0xCF, 0x61, 0xB8, 0x1E,\n     0xD3, 0x13, 0x1D, 0xA9, 0xBC, 0x87, 0x5C, 0xF5, 0x11, 0xED, 0x6C, 0x9C,\n     0x24, 0x1B, 0x24, 0x9B, 0x8E, 0x22, 0x54, 0x9A, 0x40, 0x3C, 0xE7, 0xC7,\n     0x60, 0x90, 0x02, 0xBD, 0x94, 0xDD, 0xBE, 0xBC, 0x15, 0x23, 0xB5, 0x26,\n     0x1A, 0x32, 0xDB, 0xFB, 0x9B, 0x50, 0x85, 0x19, 0x91, 0xAF, 0xCF, 0x17,\n     0x96, 0xF5, 0xDF, 0x6A, 0x72, 0xB8, 0x7F, 0xA5, 0xA3, 0x8E, 0xAD, 0xD2,\n     0x67, 0xC5, 0xD9, 0xCE, 0x6C, 0xC8, 0xBF, 0xA2, 0xF5, 0x54, 0x23, 0x1E,\n     0xB5, 0x34, 0x70, 0x3C, 0x2E, 0xC9, 0x25, 0x3A, 0x2B, 0x8C, 0x0F, 0x24,\n     0x79, 0xBB, 0xB0, 0x65, 0x9E, 0xE7, 0xA8, 0xD5, 0x93, 0xA6, 0xA5, 0x1E,\n     0x09, 0x43, 0x06, 0x6C, 0x93, 0x5C, 0xD6, 0x64, 0x68, 0xEA, 0x91, 0x55,\n     0x2B, 0xE5, 0x00, 0x3D, 0x49, 0x55, 0x52, 0x2C, 0x8D, 0xD6, 0x1D, 0x95,\n     0x72, 0xA0, 0xDE, 0x6B, 0x35, 0x1C, 0x7C, 0x70, 0x8D, 0x83, 0x26, 0x7E,\n     0x41, 0xFE, 0xC4, 0x57, 0x77, 0x1F, 0xD1, 0x47, 0x5C, 0x35, 0x60, 0xDF,\n     0x2B, 0xA6, 0x80, 0x31, 0x29, 0x20, 0x0D, 0xE8, 0xDF, 0x82, 0xB1, 0x50,\n     0x58, 0x3A, 0x8A, 0xA8, 0xF0, 0x14, 0xFE, 0xA2, 0x38, 0xCC, 0x19, 0xCB,\n     0xE2, 0x23, 0x9C, 0x21, 0xAE, 0x86, 0xA5, 0xCA, 0xDB, 0x34, 0x43, 0xA9,\n     0x51, 0xDE, 0x6C, 0x35, 0x23, 0x96, 0x7F, 0x25, 0xFD, 0xC4, 0x59, 0xB3,\n     0xCD, 0xA0, 0x4E, 0xAB, 0x0A, 0x4E, 0x77, 0x8D, 0x27, 0x11, 0x34, 0x8B,\n     0x0F, 0xA9, 0xBC, 0x89, 0xEE, 0x61, 0xC4, 0x2B, 0xC8, 0x01, 0x08, 0x1A,\n     0x24, 0xCE, 0x27, 0x94, 0x7A, 0x5C, 0x2D, 0xD5, 0xD8, 0x04, 0x26, 0xE4,\n     0x03, 0xC1, 0x52, 0x54, 0xAA, 0x8A, 0x80, 0x98, 0x11, 0xE5, 0x2F, 0xF0,\n     0xA6, 0x0A, 0x7B, 0xA4, 0xE1, 0x4B, 0xF7, 0x95, 0xA1, 0x58, 0x06, 0x31,\n     0xE2, 0xB9, 0x40, 0x05, 0x13, 0xF1, 0xD4, 0x05, 0xE2, 0xCA, 0xB0, 0xAC,\n     0x08, 0xC9, 0x0D, 0x13, 0xC5, 0x3A, 0xAA, 0xB4, 0x05, 0x55, 0x17, 0x37,\n     0x2E, 0xA7, 0x1A, 0x76, 0x9B, 0x94, 0x42, 0x95, 0x56, 0xC9, 0x0F, 0xC0,\n     0x38, 0x47, 0xC1, 0x16, 0xC9, 0xA8, 0x5E, 0x81, 0x99, 0xB5, 0x7F, 0x1A,\n     0x66, 0xF0, 0x50, 0xB3, 0xDE, 0xD1, 0xBA, 0x32, 0x39, 0x1B, 0xA8, 0xEA,\n     0x16, 0xA0, 0xFC, 0xA3, 0xA9, 0x6E, 0x25, 0x73, 0x18, 0x8C, 0xF2, 0x96,\n     0x68, 0xE3, 0xF7, 0xFA, 0xC0, 0x7F, 0x81, 0x11, 0x4C, 0x83, 0x2E, 0x3A,\n     0x6A, 0x5F, 0xAA, 0xD2, 0x3C, 0xC0, 0xC1, 0x69, 0xF9, 0xD3, 0x61, 0xB9,\n     0x2F, 0x28, 0x03, 0xE4, 0xC5, 0x62, 0x09, 0xDD, 0x0C, 0x21, 0xE0, 0x7A,\n     0xA8, 0x83, 0x52, 0xD7, 0x21, 0xC9, 0xD1, 0x63, 0x44, 0xD6, 0xF9, 0xA3,\n     0x47, 0x1C, 0xD4, 0x01, 0x31, 0x49, 0x76, 0xBE, 0x07, 0xC0, 0x70, 0xD5,\n     0x34, 0x0D, 0xC5, 0x1D, 0x08, 0x02, 0x4B, 0x22, 0x2F, 0x72, 0xB1, 0x14,\n     0x56, 0x3F, 0xD2, 0xBD, 0x68, 0xE2, 0x5F, 0x76, 0x1B, 0xC3, 0xAF, 0xED,\n     0x13, 0x35, 0x4A, 0xB7, 0x6E, 0xD3, 0xF5, 0xD4, 0x22, 0x72, 0x0A, 0x32,\n     0x39, 0x79, 0x41, 0x37, 0xC4, 0x06, 0x20, 0x78, 0x93, 0x33, 0x81, 0xE9,\n     0x13, 0x84, 0x04, 0x06, 0xDD, 0xE2, 0xEA, 0x2B, 0xF3, 0xEF, 0x55, 0x8D,\n     0xCB, 0x48, 0xC7, 0x2A, 0x8B, 0x29, 0x81, 0x2B, 0x35, 0xA4, 0x16, 0x29,\n     0x2A, 0x64, 0xAA, 0x8F, 0x09, 0xF7, 0x0C, 0xF5, 0xB8, 0x28, 0xBF, 0xCA,\n     0x02, 0xA0, 0x92, 0xBA, 0x43, 0x4B, 0x5D, 0x39, 0xCC, 0xE1, 0xE6, 0x90,\n     0xE9, 0xBB, 0x6A, 0xCA, 0xD5, 0x66, 0x92, 0xE2, 0x55, 0xBD, 0x4D, 0x01,\n     0xD1, 0x41, 0x12, 0x52, 0x69, 0x41, 0xB5, 0xA8, 0x24, 0xF8, 0x16, 0x54,\n     0x47, 0xD8, 0x43, 0xDD, 0x44, 0x2F, 0x24, 0x0F, 0xC7, 0x33, 0x46, 0x83,\n     0x37, 0xDE, 0x16, 0x2B, 0xF5, 0xD6, 0x2A, 0x6F, 0xBB, 0x94, 0xE8, 0xDF,\n     0x24, 0xCC, 0x95, 0x0C, 0x15, 0x62, 0x7E, 0x41, 0x8B, 0x1A, 0xBC, 0x9D,\n     0xBA, 0x84, 0xAA, 0x05, 0x66, 0x1F, 0xF5, 0xAD, 0x80, 0xD4, 0xF7, 0xA0,\n     0xE6, 0x51, 0xA3, 0x38, 0x79, 0x89, 0x51, 0xE0, 0x3C, 0xAA, 0x7F, 0x21,\n     0x64, 0x54, 0x1D, 0xA1, 0x83, 0x0A, 0xC4, 0xE1, 0x6E, 0x52, 0x2D, 0xD4,\n     0x38, 0xCA, 0xE5, 0xFA, 0x27, 0x34, 0x5A, 0x4A, 0xEF, 0xB8, 0x14, 0x61,\n     0x73, 0x1A, 0x8E, 0xDF, 0x98, 0xBA, 0x1B, 0x3E, 0xC6, 0x81, 0xEC, 0xE2,\n     0x5D, 0x3B, 0xA6, 0xB0, 0x06, 0xBE, 0xDC, 0x38, 0xC9, 0x69, 0x25, 0x09,\n     0xC8, 0xA1, 0x96, 0x06, 0x72, 0x40, 0x2D, 0x63, 0x65, 0xBF, 0x25, 0xD5,\n     0xF6, 0x4D, 0xD3, 0xCD, 0x69, 0xE0, 0xBC, 0x8F, 0x9B, 0xD4, 0x50, 0x3D,\n     0x48, 0x25, 0xB5, 0x00, 0x75, 0xB5, 0x07, 0x41, 0x8B, 0x1F, 0x48, 0x2D,\n     0xF5, 0xBF, 0x94, 0xBC, 0xAA, 0x8B, 0x48, 0x3C, 0x76, 0x83, 0x55, 0x41,\n     0x78, 0xA2, 0xB7, 0xD6, 0x5C, 0xE3, 0xF3, 0xD5, 0xCF, 0x7A, 0xF4, 0xE4,\n     0xDA, 0x78, 0x9E, 0x37, 0x9C, 0x7F, 0xE3, 0x19, 0x61, 0x52, 0x36, 0x7A,\n     0x5C, 0xDD, 0xD6, 0x21, 0x7D, 0xBC, 0xA8, 0x8E, 0x24, 0xB4, 0x48, 0x5C,\n     0x32, 0x08, 0xDC, 0xB7, 0x96, 0x25, 0x70, 0x85, 0x90, 0x92, 0x22, 0x38,\n     0xD4, 0x30, 0x15, 0x76, 0xB1, 0x0E, 0x36, 0xF7, 0x9A, 0x45, 0xF8, 0x18,\n     0x11, 0xD0, 0x1E, 0x92, 0xC0, 0x5F, 0x6B, 0x2E, 0x29, 0xDA, 0x1D, 0x98,\n     0x28, 0xE9, 0xA6, 0x76, 0x45, 0x06, 0x31, 0x29, 0xA3, 0x52, 0x18, 0x8F,\n     0xBA, 0xAD, 0xB9, 0x6A, 0xE6, 0xAB, 0x5E, 0x1A, 0x7B, 0x0F, 0x8F, 0x3A,\n     0x3D, 0xD5, 0x64, 0x6D, 0x55, 0x8D, 0xCD, 0x2A, 0x90, 0x88, 0xD1, 0x9A,\n     0xF1, 0xF7, 0x59, 0x0E, 0x19, 0xF0, 0xC0, 0x80, 0x0A, 0x16, 0xA6, 0xC3,\n     0xD8, 0x5D, 0x25, 0x9A, 0xCE, 0x11, 0x6C, 0xF0, 0x01, 0x5C, 0x92, 0x0C,\n     0x4C, 0xAC, 0xEA, 0x5F, 0x05, 0x28, 0x68, 0x21, 0xB9, 0x85, 0x02, 0xF7,\n     0x56, 0x00, 0xBF, 0x0A, 0x31, 0x38, 0x02, 0x9A, 0xF8, 0x42, 0xF3, 0x04,\n     0x91, 0x17, 0x85, 0xE3, 0x84, 0x48, 0x58, 0x19, 0x53, 0xAB, 0x71, 0x49,\n     0xD1, 0xA7, 0x80, 0x06, 0x1F, 0xFB, 0xDB, 0x99, 0x94, 0x42, 0x43, 0x4F,\n     0x7A, 0x6A, 0x01, 0xEB, 0x9F, 0x61, 0xF5, 0x0A, 0xFC, 0x77, 0x67, 0xAA,\n     0x91, 0x7C, 0x7F, 0x55, 0x63, 0x35, 0xD5, 0x28, 0x39, 0x45, 0x36, 0xEC,\n     0xA7, 0xF5, 0x50, 0xD8, 0x42, 0x99, 0xCA, 0x7F, 0x15, 0x28, 0x4A, 0x7B,\n     0xAA, 0xAF, 0x5D, 0xD5, 0xB7, 0x46, 0x89, 0x4F, 0x6B, 0x33, 0x01, 0x74,\n     0xC3, 0x95, 0x85, 0x7E, 0x56, 0xE4, 0xE9, 0x94, 0xE3, 0xED, 0xE9, 0x9E,\n     0xA7, 0x89, 0xA7, 0xBD, 0xFA, 0x11, 0x9B, 0xC2, 0x34, 0xED, 0x45, 0x0D,\n     0x40, 0xC1, 0x1A, 0x15, 0xD2, 0xD1, 0xAE, 0x1B, 0xE4, 0xE9, 0x55, 0x9B,\n     0x77, 0x7F, 0xA0, 0xD2, 0x51, 0x73, 0x4D, 0xE0, 0x95, 0x30, 0xED, 0x16,\n     0xDB, 0xD4, 0xA7, 0x4F, 0x03, 0xBF, 0x61, 0x6C, 0xF5, 0xCF, 0x40, 0x59,\n     0x32, 0xEA, 0x52, 0x7D, 0xB0, 0xD1, 0xA3, 0xB4, 0x66, 0xD4, 0x82, 0xEC,\n     0x94, 0x8D, 0x75, 0x02, 0xA2, 0x13, 0x1F, 0xE9, 0x46, 0x77, 0xD5, 0x35,\n     0xAD, 0xD9, 0x10, 0x5F, 0xF4, 0x5D, 0xE6, 0xD0, 0xB1, 0x9D, 0xDA, 0xA8,\n     0x57, 0xA7, 0xF3, 0x10, 0x46, 0xEA, 0x3E, 0x35, 0x92, 0x31, 0xEB, 0x0C,\n     0xC3, 0x9A, 0x99, 0x43, 0x87, 0x1B, 0xDE, 0x32, 0xB1, 0xA8, 0x78, 0x47,\n     0xE3, 0xBB, 0x47, 0x45, 0x6E, 0x7C, 0xD3, 0x13, 0xB0, 0xB6, 0x7A, 0x80,\n     0x58, 0x31, 0xBD, 0x18, 0xA1, 0xD4, 0x6D, 0xA1, 0xA1, 0xEA, 0x45, 0xE9,\n     0x0F, 0x98, 0x67, 0x82, 0xA7, 0x5B, 0xBA, 0xEE, 0x8B, 0xB4, 0x4A, 0x68,\n     0x20, 0x8D, 0xEB, 0xCA, 0x48, 0x35, 0x61, 0xCC, 0x14, 0xB7, 0x87, 0x47,\n     0x51, 0x58, 0x52, 0xA7, 0x6A, 0xE6, 0xAB, 0xEB, 0x61, 0x2A, 0xA1, 0xFF,\n     0x7C, 0x74, 0xA3, 0x45, 0x2D, 0x29, 0xCA, 0x9A, 0x8A, 0x1A, 0x80, 0x9F,\n     0x14, 0x29, 0xBF, 0x76, 0x2C, 0xD6, 0xF2, 0x01, 0x86, 0xBD, 0x4E, 0xF2,\n     0x83, 0x91, 0x79, 0x79, 0x7B, 0xFF, 0x49, 0xFD, 0x54, 0xB6, 0xB6, 0x3F,\n     0xC7, 0x12, 0x6A, 0x68, 0xD9, 0xB9, 0x04, 0x22, 0x82, 0xEC, 0x8F, 0xB5,\n     0x49, 0x76, 0x79, 0xCF, 0x22, 0x82, 0x1D, 0x75, 0x04, 0xD9, 0x82, 0x3B,\n     0x2C, 0x8D, 0x74, 0x4B, 0xF9, 0xDC, 0xD2, 0x81, 0x9E, 0x15, 0xDC, 0xD1,\n     0xF1, 0x89, 0x84, 0x02, 0x9A, 0xC2, 0xEC, 0x94, 0x14, 0x59, 0x6F, 0x0F,\n     0x7B, 0xCD, 0xAF, 0x6E, 0xF9, 0xF4, 0x79, 0xA1, 0xB0, 0x18, 0x55, 0x69,\n     0x62, 0x76, 0x84, 0xF2, 0x71, 0x03, 0xAC, 0xFF, 0x76, 0x40, 0x5C, 0xF1,\n     0x00, 0x01, 0xA6, 0x51, 0xA4, 0xA4, 0x3C, 0xBB, 0x52, 0xDA, 0x2A, 0x52,\n     0x2B, 0x35, 0xB1, 0x87, 0xBF, 0x3D, 0xBF, 0x0D, 0xB2, 0x12, 0x92, 0x75,\n     0xFB, 0x11, 0xD4, 0xD7, 0x8F, 0xBE, 0xD1, 0xF5, 0xB1, 0x49, 0xB8, 0x27,\n     0x6A, 0x74, 0x87, 0x3B, 0xC1, 0x81, 0x04, 0xF3, 0x46, 0x54, 0xE0, 0x54,\n     0x2B, 0xF0, 0x86, 0xF7, 0x58, 0x6D, 0xA9, 0x77, 0x8D, 0xE9, 0xDB, 0x0B,\n     0x65, 0xE8, 0xCA, 0xD5, 0x4C, 0xCE, 0xAC, 0xAC, 0x59, 0xA8, 0x25, 0x7A,\n     0x4D, 0x4B, 0xFB, 0xDB, 0x57, 0x27, 0x7E, 0xF1, 0x2D, 0x1C, 0x2A, 0xF9,\n     0x89, 0x33, 0xD1, 0x24, 0x4B, 0x3B, 0x43, 0xD0, 0xE0, 0x5D, 0xC0, 0x26,\n     0xCD, 0x93, 0xD4, 0x8C, 0xB5, 0x5E, 0x2C, 0x85, 0xEA, 0x88, 0x47, 0x54,\n     0x66, 0x29, 0x2D, 0xE8, 0x5D, 0x6F, 0x75, 0xE5, 0x76, 0x51, 0x6D, 0x8C,\n     0x5D, 0xD4, 0x34, 0xBA, 0xCA, 0x9D, 0x59, 0x90, 0x12, 0x84, 0x59, 0x9A,\n     0xCD, 0xEB, 0xEC, 0x8B, 0x30, 0x27, 0x12, 0x59, 0x19, 0x45, 0xCD, 0xCB,\n     0x5C, 0xF8, 0x6A, 0xF4, 0xA1, 0x44, 0x52, 0xD4, 0x52, 0x80, 0xB0, 0x3A,\n     0xD7, 0xF1, 0xDF, 0xD4, 0x91, 0x86, 0x92, 0xF6, 0x5A, 0xFA, 0x77, 0x4A,\n     0xCD, 0x18, 0xC2, 0xD2, 0xA1, 0x82, 0xF5, 0xA4, 0xA9, 0xE3, 0xD8, 0x8A,\n     0xBA, 0x3D, 0xD6, 0x38, 0xAA, 0x60, 0xCD, 0x5B, 0xF9, 0xBF, 0xC1, 0x2A,\n     0x50, 0x29, 0x2E, 0xA9, 0x59, 0x54, 0x63, 0x22, 0x6E, 0xA9, 0xE1, 0x51,\n     0xE4, 0xE9, 0x74, 0x23, 0xBD, 0xEE, 0x4D, 0x21, 0xAB, 0x44, 0xB5, 0xBA,\n     0x57, 0xB8, 0x9D, 0x84, 0x28, 0xE5, 0x94, 0x97, 0xA9, 0xBF, 0xBD, 0x87,\n     0x3F, 0x13, 0x50, 0x2B, 0x80, 0xCC, 0x43, 0x05, 0x94, 0x48, 0x20, 0xC0,\n     0xC2, 0x41, 0x8C, 0xA6, 0x13, 0x1F, 0x8A, 0xFE, 0x91, 0xA8, 0xDA, 0x84,\n     0xB7, 0x24, 0x28, 0xAB, 0x96, 0xA6, 0xD1, 0x41, 0x56, 0x56, 0xB9, 0x8B,\n     0x82, 0xA7, 0xC4, 0xD0, 0xA9, 0x56, 0x3E, 0xCE, 0x5A, 0x8E, 0xA4, 0xA7,\n     0x94, 0xA9, 0x0A, 0x2F, 0x3B, 0xD1, 0x8A, 0x56, 0x71, 0x74, 0x64, 0xC4,\n     0xB6, 0x23, 0xD5, 0xB8, 0x8D, 0xC2, 0xAC, 0xD5, 0xD0, 0xA1, 0xFC, 0xF4,\n     0xF4, 0xBE, 0x9B, 0x1A, 0x1F, 0x27, 0x1F, 0x35, 0x5F, 0x4E, 0x75, 0x51,\n     0x20, 0x56, 0x02, 0xDA, 0x1C, 0xF1, 0x11, 0x75, 0x65, 0xDC, 0x43, 0x25,\n     0x70, 0x55, 0x9D, 0x10, 0xC6, 0x5A, 0x2C, 0xC8, 0xBA, 0x6D, 0x6F, 0x23,\n     0xBA, 0x95, 0x57, 0x42, 0x1C, 0xF0, 0x22, 0x2F, 0x8C, 0x1E, 0x26, 0x7A,\n     0xDF, 0x4F, 0x57, 0xBA, 0xF8, 0x57, 0x55, 0xA6, 0x0A, 0xC0, 0x47, 0xDF,\n     0x4E, 0x00, 0x27, 0x4B, 0x61, 0x7D, 0x5B, 0x5A, 0xC2, 0x54, 0x71, 0x1F,\n     0x76, 0xCC, 0x7C, 0x1F, 0xEB, 0x11, 0x50, 0x3C, 0x53, 0x5B, 0x64, 0x21,\n     0xA6, 0x8E, 0x98, 0x32, 0xC8, 0x37, 0x8A, 0x03, 0x6F, 0x85, 0x21, 0xEF,\n     0x31, 0x91, 0x9A, 0x7C, 0x36, 0x1B, 0xC9, 0x0A, 0x83, 0x0E, 0xE4, 0xAF,\n     0x34, 0x8E, 0x9A, 0xDA, 0x29, 0x53, 0x36, 0xEB, 0x3B, 0x18, 0x53, 0xFD,\n     0x7F, 0xED, 0x14, 0x96, 0xF0, 0xB2, 0x97, 0x49, 0xD7, 0xE3, 0xBC, 0x58,\n     0x67, 0xFA, 0x38, 0x7B, 0xFF, 0x85, 0xEE, 0x86, 0x67, 0xC1, 0x0F, 0xD0,\n     0x81, 0x81, 0xBB, 0x24, 0x89, 0xDA, 0xC5, 0xA0, 0x25, 0x38, 0xE6, 0x06,\n     0xB0, 0xD2, 0x27, 0x25, 0x68, 0xD3, 0xA9, 0x47, 0xCD, 0xE5, 0xD1, 0xA1,\n     0x87, 0x4A, 0xD6, 0x3F, 0xDA, 0xCA, 0xEA, 0x47, 0xF5, 0x57, 0xA1, 0xBF,\n     0x4D, 0x2E, 0x28, 0x9B, 0x30, 0xD0, 0x31, 0x7B, 0x85, 0xCF, 0xED, 0xF8,\n     0x16, 0xEE, 0x47, 0x83, 0xAB, 0xB3, 0x8C, 0xD8, 0xCE, 0x65, 0xAA, 0x9F,\n     0x2D, 0xA3, 0x40, 0x47, 0x48, 0x3C, 0xEA, 0x76, 0x90, 0xB9, 0x6F, 0x84,\n     0x48, 0x9C, 0x6A, 0x32, 0xA2, 0x9A, 0xA6, 0x7A, 0xF1, 0xD0, 0x99, 0xF5,\n     0x04, 0x88, 0x86, 0x64, 0x87, 0xEA, 0xD1, 0x5D, 0x46, 0x54, 0x77, 0x54,\n     0x3C, 0xA7, 0x7A, 0x53, 0x67, 0x4C, 0xC2, 0x93, 0x63, 0x76, 0x8C, 0x14,\n     0xA3, 0x89, 0x58, 0x87, 0x8F, 0xF7, 0x9B, 0x93, 0xED, 0xED, 0x84, 0x01,\n     0x20, 0xEA, 0x58, 0xB2, 0xA6, 0x37, 0x09, 0xBB, 0x43, 0x25, 0xAA, 0x79,\n     0xE3, 0x4D, 0x9B, 0x0C, 0xE5, 0x27, 0x20, 0x37, 0x74, 0xCC, 0xD0, 0x07,\n     0x80, 0x90, 0x48, 0xF9, 0xBD, 0xAC, 0x59, 0x95, 0xD5, 0xEE, 0x06, 0x1D,\n     0x8C, 0x24, 0x33, 0x49, 0xA3, 0x2F, 0x6C, 0x29, 0x09, 0x92, 0xCA, 0x26,\n     0xF7, 0x7D, 0x59, 0xEE, 0x28, 0xBD, 0x25, 0x7F, 0x01, 0x12, 0x52, 0x8C,\n     0x6C, 0x94, 0x01, 0x75, 0x2E, 0xAB, 0x97, 0x01, 0x0C, 0x35, 0x6C, 0x84,\n     0x7A, 0x2B, 0x2A, 0xE2, 0x61, 0x03, 0x75, 0x49, 0xE5, 0xA5, 0xF8, 0x28,\n     0x8E, 0x9E, 0xED, 0xA5, 0x07, 0xD5, 0x9D, 0xE7, 0x2D, 0x54, 0x11, 0xEA,\n     0x59, 0x92, 0x52, 0x3A, 0x51, 0x34, 0xF9, 0x50, 0x17, 0x68, 0xA9, 0x8A,\n     0xD2, 0xA3, 0x02, 0xDD, 0xAB, 0x88, 0x11, 0x18, 0xCD, 0xA1, 0x03, 0xF8,\n     0x9D, 0xED, 0x70, 0xAB, 0x3B, 0x22, 0x0B, 0x75, 0x51, 0x94, 0x18, 0x18,\n     0x91, 0xA2, 0x95, 0x2A, 0x0B, 0x8A, 0x20, 0x36, 0x8F, 0x47, 0xF0, 0xC0,\n     0x22, 0xDE, 0x8E, 0xEE, 0x7A, 0x53, 0x4D, 0xA5, 0x33, 0xBC, 0xA1, 0xAF,\n     0x32, 0xCA, 0xF7, 0x4C, 0x61, 0xA8, 0x84, 0x50, 0xB1, 0x33, 0xC8, 0x92,\n     0xB3, 0x5A, 0x45, 0x8F, 0x5A, 0x7B, 0x3A, 0xA0, 0x1C, 0xEF, 0x99, 0xF8,\n     0x47, 0xEA, 0x60, 0xD2, 0xEC, 0xA7, 0xA7, 0xC8, 0x5A, 0xAD, 0x52, 0x63,\n     0x0F, 0x51, 0x5D, 0x7D, 0x0B, 0xBF, 0xB9, 0xA5, 0x98, 0xF4, 0xB5, 0x09,\n     0x8F, 0xAC, 0x89, 0xD2, 0x74, 0x1B, 0x5D, 0xE4, 0x57, 0x6D, 0x3A, 0x4F,\n     0x4C, 0xEA, 0x91, 0x68, 0x24, 0xD6, 0xBA, 0xDE, 0xBA, 0xB1, 0xAB, 0x82,\n     0x92, 0x7E, 0xD0, 0x4E, 0x18, 0x00, 0x1B, 0x13, 0x52, 0x49, 0x67, 0xB4,\n     0x31, 0x88, 0x0F, 0xCE, 0x75, 0xF2, 0xFD, 0x21, 0xBC, 0xAB, 0xCE, 0x99,\n     0x83, 0x28, 0x9A, 0x9B, 0xB9, 0xB4, 0x8B, 0x18, 0xAF, 0x76, 0xD5, 0xF4,\n     0xAD, 0x88, 0x0A, 0x5A, 0x79, 0x21, 0x9F, 0x0E, 0xB9, 0xB3, 0x07, 0x34,\n     0x19, 0x93, 0x38, 0x79, 0x8F, 0x18, 0x97, 0x6D, 0xCD, 0xBF, 0xDF, 0xE0,\n     0x40, 0x69, 0xDA, 0xB7, 0xC2, 0xD4, 0xB0, 0x39, 0xD6, 0x20, 0x0D, 0x40,\n     0xF8, 0x52, 0x05, 0x57, 0x23, 0x09, 0x1E, 0x09, 0x64, 0x4C, 0x8D, 0x4F,\n     0xA0, 0x8F, 0xA1, 0xAE, 0xAF, 0x11, 0x64, 0x9F, 0x76, 0x32, 0x0D, 0x8B,\n     0xB0, 0x91, 0xA3, 0x2A, 0xF0, 0xDD, 0xF5, 0xFA, 0x48, 0x12, 0x2A, 0x3A,\n     0x05, 0x31, 0xB7, 0x4A, 0x0E, 0x23, 0xB4, 0x49, 0xE0, 0x10, 0x74, 0x64,\n     0x43, 0xAD, 0x7A, 0xFC, 0x54, 0xF3, 0x12, 0x2B, 0xFC, 0xD1, 0xE1, 0x74,\n     0xE8, 0x94, 0x33, 0x06, 0x85, 0x5E, 0x61, 0x27, 0xE5, 0xB1, 0x4E, 0xDD,\n     0x7C, 0x1B, 0x48, 0xD8, 0xDF, 0x04, 0xEB, 0x54, 0x0B, 0x99, 0x08, 0x58,\n     0x6F, 0x6B, 0x60, 0x7F, 0xFA, 0x01, 0x3F, 0x7E, 0x85, 0xE6, 0xEF, 0xBF,\n     0x12, 0x93, 0x9F, 0x75, 0x1F, 0xF1, 0x41, 0xB5, 0x8A, 0x60, 0x8F, 0x51,\n     0x54, 0xD4, 0x45, 0x34, 0x64, 0x2A, 0xAA, 0x36, 0xC8, 0x05, 0x9C, 0xF0,\n     0xF8, 0x0F, 0x91, 0xB5, 0x74, 0x5C, 0x53, 0x65, 0x60, 0x7E, 0x7F, 0xD4,\n     0x7B, 0xB5, 0x2E, 0x1B, 0x37, 0x06, 0xE8, 0xF5, 0x51, 0x8F, 0xA0, 0x2E,\n     0x50, 0x47, 0xAD, 0x5C, 0xF9, 0x51, 0x15, 0x7F, 0x7D, 0xBD, 0x28, 0x69,\n     0x87, 0xBC, 0x6C, 0x9E, 0x68, 0x46, 0xC5, 0x05, 0xF7, 0x0E, 0xC0, 0x1F,\n     0x9B, 0x02, 0xAD, 0x68, 0x4D, 0xC5, 0xAB, 0xD5, 0x4E, 0x2A, 0x2C, 0x7D,\n     0x09, 0xC6, 0x2A, 0x58, 0xAF, 0x32, 0x45, 0x6C, 0x4D, 0x2D, 0xCB, 0xDE,\n     0x49, 0x77, 0xD1, 0x6C, 0x24, 0x7C, 0x6A, 0xEF, 0xC8, 0x9F, 0xD6, 0x65,\n     0x6C, 0x06, 0x81, 0x8C, 0xDE, 0x83, 0x6F, 0x85, 0x81, 0xC6, 0x7A, 0x66,\n     0x57, 0x4E, 0x1D, 0x2F, 0x02, 0x39, 0x06, 0xD3, 0x3D, 0xEC, 0xF7, 0x9A,\n     0x5D, 0xB2, 0x84, 0xA9, 0x4E, 0xA4, 0x4D, 0x74, 0xBC, 0xFA, 0xC4, 0xE9,\n     0xBB, 0x0E, 0x28, 0x97, 0x71, 0x73, 0xC5, 0x0C, 0x33, 0x3C, 0x08, 0xAA,\n     0xD6, 0x12, 0x84, 0xDF, 0xE7, 0x7B, 0xFA, 0xE8, 0x63, 0x7A, 0x63, 0xFD,\n     0x3D, 0x85, 0xE4, 0x5A, 0x48, 0x38, 0x5D, 0x7D, 0x2D, 0xA6, 0x57, 0xEB,\n     0x8A, 0x8E, 0x72, 0x8E, 0x93, 0xE9, 0xE0, 0xCC, 0x6C, 0x68, 0xB4, 0x3D,\n     0x75, 0xE1, 0x59, 0x00, 0x50, 0xB3, 0x3A, 0x4B, 0x27, 0x55, 0x36, 0x58,\n     0x7F, 0x5E, 0xF9, 0x7E, 0x93, 0xDF, 0x8F, 0x36, 0x17, 0x7E, 0x7A, 0x3F,\n     0x3B, 0x89, 0x19, 0xC4, 0xBF, 0x7F, 0xBF, 0x40, 0x05, 0x38, 0x28, 0x17,\n     0x75, 0x48, 0x90, 0x74, 0x0A, 0x00, 0xE8, 0x97, 0x4D, 0xF6, 0x7E, 0x20,\n     0x44, 0x6D, 0x59, 0xDD, 0xAB, 0x55, 0x10, 0xEE, 0x57, 0x56, 0x48, 0xC0,\n     0x3D, 0xF2, 0xC0, 0x75, 0x0A, 0x6B, 0x3B, 0x83, 0x63, 0xE9, 0x92, 0xB9,\n     0x5B, 0xDF, 0xE6, 0x3C, 0x0B, 0xAE, 0x7E, 0xCF, 0x48, 0x9B, 0xD5, 0xB8,\n     0x50, 0xFD, 0xCB, 0x2F, 0xA1, 0xD6, 0xF7, 0xDB, 0x4A, 0x87, 0x72, 0xCB,\n     0x0E, 0xF8, 0x54, 0x00, 0x96, 0x71, 0x5D, 0xEA, 0x7F, 0x32, 0x0D, 0x2A,\n     0xAF, 0xA9, 0x64, 0x8E, 0x96, 0xAB, 0xA1, 0x20, 0xA2, 0xAA, 0x02, 0x86,\n     0x0A, 0x4F, 0x5F, 0x5A, 0x62, 0x1A, 0x1D, 0x3F, 0xC0, 0x8D, 0xFA, 0x76,\n     0x1B, 0xE4, 0x31, 0xA5, 0x31, 0xF0, 0x27, 0x3E, 0x85, 0x8E, 0x0A, 0x25,\n     0x30, 0x6E, 0x0F, 0x4F, 0x45, 0xC7, 0xBD, 0x25, 0x59, 0x09, 0xFF, 0x5D,\n     0x27, 0xE2, 0xBF, 0x79, 0xFE, 0xA7, 0x81, 0xD4, 0x48, 0xEE, 0xEE, 0xFF,\n     0x01, 0x5C, 0x24, 0xED, 0x9E, 0x3F, 0x94, 0x9F, 0x81, 0x00, 0x00, 0x02,\n     0xFA, 0x50, 0x4C, 0x54, 0x45, 0x47, 0x70, 0x4C, 0xC0, 0xB9, 0xB0, 0x90,\n     0x88, 0x74, 0x6A, 0x5E, 0x4B, 0x78, 0x71, 0x68, 0x78, 0x6F, 0x66, 0x74,\n     0x69, 0x5E, 0x97, 0x92, 0x85, 0xB6, 0xAD, 0x9F, 0xC9, 0xC3, 0xB9, 0xB8,\n     0xB1, 0xA4, 0x82, 0x7A, 0x74, 0x8D, 0x7E, 0x72, 0xC9, 0xC1, 0xB1, 0x88,\n     0x7C, 0x6F, 0xAD, 0xA5, 0x96, 0x65, 0x58, 0x4D, 0xC7, 0xBA, 0xAB, 0xCE,\n     0xCC, 0xC8, 0xA9, 0xA2, 0x9C, 0x79, 0x6B, 0x61, 0xD4, 0xD0, 0xCA, 0x78,\n     0x67, 0x59, 0x94, 0x91, 0x80, 0xCB, 0xBA, 0xA7, 0x6C, 0x64, 0x52, 0x99,\n     0x91, 0x84, 0xB4, 0xAB, 0x9C, 0xA2, 0x9C, 0x8B, 0xB0, 0xA6, 0x94, 0xA0,\n     0x96, 0x82, 0x6E, 0x6D, 0x59, 0xC8, 0xC3, 0xBD, 0xA5, 0xA1, 0x84, 0x89,\n     0x87, 0x68, 0x83, 0x85, 0x62, 0x7B, 0x6F, 0x60, 0x62, 0x54, 0x4A, 0x6F,\n     0x67, 0x53, 0xBB, 0xAB, 0x97, 0xC0, 0xB0, 0x9D, 0xAC, 0x9F, 0x85, 0x85,\n     0x79, 0x71, 0x97, 0x93, 0x8F, 0xC7, 0xBD, 0xB6, 0x8F, 0x9D, 0x7F, 0x31,\n     0x2B, 0x29, 0xA8, 0x9E, 0x8B, 0xC7, 0xBF, 0xB5, 0x93, 0x8B, 0x73, 0x5E,\n     0x5A, 0x40, 0x81, 0x6F, 0x5E, 0xB1, 0x9E, 0x91, 0x8A, 0x7E, 0x6A, 0xA5,\n     0x96, 0x7F, 0x72, 0x69, 0x50, 0xAF, 0xA2, 0x8A, 0x48, 0x40, 0x3A, 0x51,\n     0x4E, 0x4C, 0x3C, 0x33, 0x30, 0x52, 0x46, 0x3B, 0x86, 0x77, 0x70, 0xC3,\n     0xBC, 0xA6, 0x51, 0x45, 0x40, 0xC7, 0xBB, 0xAA, 0x56, 0x45, 0x3D, 0xC0,\n     0xB8, 0x9C, 0xCB, 0xBB, 0xAF, 0xB3, 0x9B, 0x87, 0x61, 0x5E, 0x4E, 0x93,\n     0x88, 0x7F, 0xB3, 0xA3, 0x89, 0x6B, 0x71, 0x57, 0x6A, 0x71, 0x52, 0x60,\n     0x6A, 0x52, 0x7D, 0x77, 0x6F, 0xBC, 0xAB, 0x97, 0x8A, 0x7A, 0x65, 0xB9,\n     0xAD, 0x97, 0x2E, 0x6D, 0x6E, 0x56, 0x44, 0x36, 0x2E, 0x23, 0x19, 0x87,\n     0x6D, 0x5D, 0x99, 0x7F, 0x6E, 0x69, 0x59, 0x42, 0x67, 0x57, 0x3F, 0x6B,\n     0x57, 0x49, 0x64, 0x54, 0x3E, 0xAC, 0x93, 0x81, 0x8C, 0x75, 0x66, 0x6E,\n     0x63, 0x48, 0x5E, 0x4F, 0x3B, 0x5D, 0x4A, 0x3A, 0x62, 0x51, 0x41, 0x9C,\n     0x8F, 0x73, 0x51, 0x41, 0x2D, 0x54, 0x4A, 0x31, 0x7E, 0x66, 0x59, 0x78,\n     0x67, 0x51, 0x67, 0x5C, 0x45, 0x66, 0x50, 0x42, 0x7E, 0x6F, 0x52, 0x68,\n     0x57, 0x46, 0x97, 0x7C, 0x6A, 0xA0, 0x87, 0x78, 0xAD, 0x96, 0x86, 0x65,\n     0x54, 0x44, 0x71, 0x5F, 0x4A, 0x81, 0x72, 0x58, 0x95, 0x79, 0x68, 0x6B,\n     0x5D, 0x44, 0xB2, 0x99, 0x88, 0x8E, 0x80, 0x66, 0x9A, 0x8A, 0x73, 0x71,\n     0x60, 0x51, 0x62, 0x4E, 0x40, 0x5A, 0x4A, 0x36, 0x5E, 0x4E, 0x37, 0xB3,\n     0xA1, 0x89, 0x6A, 0x53, 0x45, 0x6C, 0x5B, 0x48, 0x7C, 0x62, 0x52, 0x94,\n     0x7F, 0x6E, 0x59, 0x50, 0x39, 0x6E, 0x5E, 0x49, 0x81, 0x6E, 0x60, 0x42,\n     0x38, 0x26, 0x87, 0x76, 0x5E, 0x83, 0x77, 0x5C, 0x58, 0x46, 0x34, 0x5F,\n     0x4C, 0x3E, 0x43, 0x33, 0x27, 0x5A, 0x4A, 0x3C, 0x5A, 0x45, 0x3A, 0x33,\n     0x2C, 0x1F, 0x77, 0x5E, 0x4E, 0x6D, 0x5C, 0x50, 0xA7, 0x91, 0x82, 0x3E,\n     0x32, 0x24, 0x8F, 0x79, 0x67, 0x5E, 0x4F, 0x42, 0x8A, 0x70, 0x60, 0x7A,\n     0x6A, 0x5B, 0x99, 0x81, 0x71, 0x9D, 0x86, 0x75, 0xA6, 0x8D, 0x7D, 0x93,\n     0x85, 0x68, 0xB8, 0x9F, 0x8C, 0x55, 0x41, 0x34, 0xBC, 0xA3, 0x92, 0x47,\n     0x37, 0x2C, 0x76, 0x61, 0x53, 0x72, 0x62, 0x4D, 0x81, 0x6A, 0x5B, 0x50,\n     0x43, 0x31, 0x39, 0x2F, 0x26, 0xA3, 0x8A, 0x79, 0xAD, 0x9E, 0x86, 0x7B,\n     0x6D, 0x55, 0x94, 0x7B, 0x6B, 0x56, 0x49, 0x36, 0x8E, 0x73, 0x60, 0x87,\n     0x72, 0x64, 0x84, 0x68, 0x59, 0x92, 0x77, 0x66, 0x52, 0x46, 0x35, 0x8A,\n     0x79, 0x60, 0x89, 0x7E, 0x61, 0xAE, 0x97, 0x80, 0xBE, 0xAC, 0x96, 0x4A,\n     0x3B, 0x28, 0x3D, 0x2C, 0x1C, 0x4F, 0x3C, 0x2E, 0x6D, 0x59, 0x44, 0xBE,\n     0xA6, 0x96, 0x26, 0x1A, 0x13, 0x90, 0x76, 0x63, 0x47, 0x40, 0x2D, 0x98,\n     0x87, 0x78, 0x74, 0x69, 0x4D, 0x4B, 0x3F, 0x35, 0x1F, 0x1E, 0x19, 0x63,\n     0x59, 0x4B, 0x77, 0x65, 0x57, 0x97, 0x87, 0x6A, 0xA4, 0x94, 0x7B, 0xA2,\n     0x91, 0x77, 0x7B, 0x6B, 0x4F, 0x92, 0x81, 0x67, 0x95, 0x86, 0x6E, 0xC0,\n     0xAA, 0x9C, 0x1E, 0x14, 0x0F, 0x59, 0x4D, 0x34, 0x9C, 0x8B, 0x79, 0x75,\n     0x5D, 0x50, 0x7E, 0x66, 0x54, 0xB4, 0xA7, 0x8B, 0xA2, 0x8C, 0x7B, 0x16,\n     0x0E, 0x0A, 0x74, 0x59, 0x4A, 0xA7, 0x99, 0x7D, 0x99, 0x84, 0x6D, 0x70,\n     0x59, 0x4B, 0x86, 0x73, 0x58, 0xAF, 0xA3, 0x89, 0x1D, 0x17, 0x15, 0x68,\n     0x5D, 0x4A, 0xA1, 0x9A, 0x7A, 0x9E, 0x93, 0x78, 0x36, 0x26, 0x1D, 0xCA,\n     0xB9, 0xA5, 0x82, 0x67, 0x55, 0x9E, 0x84, 0x72, 0xB5, 0x9B, 0x8A, 0xAC,\n     0xA6, 0x87, 0x5F, 0x58, 0x3E, 0xAF, 0xA9, 0xA3, 0xA8, 0x98, 0x88, 0xB5,\n     0xAA, 0x8C, 0x71, 0x6B, 0x5D, 0xAE, 0xA4, 0x9C, 0x7E, 0x78, 0x72, 0x75,\n     0x70, 0x6E, 0x6E, 0x65, 0x50, 0x87, 0x7C, 0x71, 0xB8, 0xAD, 0x90, 0x55,\n     0x47, 0x3D, 0x8A, 0x78, 0x6B, 0x69, 0x4B, 0x41, 0x7B, 0x78, 0x58, 0x71,\n     0x68, 0x54, 0x8F, 0x89, 0x69, 0x73, 0x77, 0x55, 0xC3, 0xB4, 0xA0, 0x40,\n     0x3B, 0x2D, 0x80, 0x75, 0x67, 0x5E, 0x5F, 0x41, 0x4B, 0x47, 0x46, 0xAB,\n     0x9C, 0x83, 0x5C, 0x54, 0x48, 0x94, 0x8E, 0x8B, 0x65, 0x6A, 0x49, 0x5E,\n     0x42, 0x38, 0xBE, 0xBC, 0xBD, 0x99, 0x90, 0x89, 0x99, 0x96, 0x94, 0x32,\n     0x17, 0x13, 0x84, 0x80, 0x7F, 0x3C, 0x36, 0x36, 0x42, 0x1E, 0x1A, 0x82,\n     0x87, 0x67, 0x9E, 0x9D, 0x9C, 0xB3, 0xB0, 0xB1, 0x7D, 0x58, 0x49, 0xC8,\n     0xD1, 0x08, 0xD9, 0x00, 0x00, 0x00, 0x4F, 0x74, 0x52, 0x4E, 0x53, 0x00,\n     0x04, 0xB6, 0xCA, 0x1D, 0x30, 0x8E, 0x0A, 0x15, 0x0E, 0x5D, 0x93, 0x7E,\n     0x62, 0x49, 0x27, 0xA5, 0x98, 0x6D, 0x68, 0xAC, 0x31, 0xC8, 0x45, 0xF8,\n     0xBA, 0x86, 0x84, 0x34, 0xAA, 0xE5, 0x70, 0x95, 0x95, 0xC1, 0xFD, 0xFB,\n     0xDF, 0xEB, 0xDC, 0x64, 0xF3, 0xBB, 0x76, 0x45, 0x3C, 0xFC, 0x59, 0x7B,\n     0xF3, 0xF1, 0xDF, 0x94, 0xDF, 0xBB, 0xD7, 0xEB, 0xAA, 0x40, 0xDE, 0xE0,\n     0x63, 0x30, 0x7F, 0xB8, 0xEF, 0x6E, 0xB2, 0xCA, 0x8F, 0x96, 0xC5, 0x9B,\n     0x82, 0x59, 0xE6, 0xF1, 0xFB, 0x9B, 0x80, 0x90, 0xD3, 0xAB, 0x00, 0x00,\n     0x05, 0xFC, 0x49, 0x44, 0x41, 0x54, 0x18, 0x19, 0xED, 0xC1, 0x65, 0x50,\n     0x9B, 0x07, 0x18, 0x00, 0xE0, 0x17, 0x0A, 0x04, 0xEA, 0xEE, 0xDE, 0xCE,\n     0xDD, 0xDD, 0x5D, 0xEF, 0x8B, 0x7B, 0x88, 0xBB, 0x12, 0x20, 0x42, 0x8C,\n     0x10, 0x2C, 0x48, 0x20, 0x10, 0x24, 0xB8, 0x53, 0xDC, 0xDD, 0xA1, 0x14,\n     0x2B, 0x50, 0xA1, 0xBA, 0x96, 0xCA, 0x2A, 0x5B, 0x3B, 0x77, 0xBB, 0xDB,\n     0x6E, 0x7F, 0x37, 0xD6, 0x8F, 0xED, 0xCF, 0xEE, 0xB6, 0xE7, 0x81, 0xFF,\n     0x8C, 0xC0, 0x55, 0xF0, 0x8F, 0xF8, 0x3C, 0x76, 0x71, 0x07, 0x06, 0xFE,\n     0x81, 0x5D, 0xD7, 0xC2, 0x67, 0x9E, 0x0C, 0x84, 0xBF, 0xCD, 0xFF, 0xBD,\n     0x19, 0xD6, 0xCC, 0x99, 0x6D, 0x00, 0x81, 0xB0, 0x78, 0x9B, 0x9E, 0xD8,\n     0xEB, 0xFB, 0xC4, 0xB5, 0x59, 0xCD, 0xCC, 0xCC, 0x1E, 0x58, 0x79, 0xD7,\n     0x1B, 0x81, 0x80, 0x16, 0x06, 0x7E, 0xB7, 0xCB, 0x7D, 0x65, 0x1F, 0x4B,\n     0xD8, 0x3B, 0x3B, 0x38, 0x7B, 0x62, 0x0F, 0x6C, 0xEC, 0x0C, 0xBF, 0x13,\n     0xD0, 0xC1, 0x2C, 0xBF, 0x6B, 0x3B, 0xFC, 0xC6, 0xEF, 0x61, 0x75, 0xBE,\n     0xBB, 0x43, 0x36, 0xAB, 0xE9, 0xD5, 0x8C, 0x3C, 0xEE, 0x87, 0x1B, 0x3B,\n     0xB3, 0x03, 0x03, 0x68, 0xF8, 0xF9, 0x0E, 0x0E, 0x3E, 0xB4, 0x29, 0x08,\n     0x60, 0x35, 0xAD, 0x57, 0xD9, 0x53, 0xC1, 0xD5, 0x9C, 0x66, 0xCD, 0x4E,\n     0xED, 0x7C, 0xA4, 0x67, 0x6A, 0x64, 0xE4, 0x4E, 0x40, 0x23, 0xC0, 0xC4,\n     0xBD, 0x70, 0xA1, 0x69, 0xE7, 0x03, 0xFE, 0xC2, 0xE9, 0x5E, 0xA5, 0xBA,\n     0xC2, 0xAA, 0x49, 0xE9, 0xCD, 0xCB, 0xEB, 0x65, 0x75, 0xE5, 0x9D, 0x38,\n     0xFA, 0x32, 0xA0, 0xB1, 0x3A, 0x52, 0x99, 0x7C, 0xE3, 0xF2, 0x7C, 0xFE,\n     0x34, 0x53, 0x49, 0x55, 0xF6, 0xD8, 0x6C, 0x5D, 0x05, 0x9D, 0x9F, 0x8E,\n     0x59, 0xC7, 0xA6, 0x2E, 0x9E, 0x39, 0xB0, 0x02, 0xD0, 0x58, 0x42, 0x0F,\n     0xBD, 0xDA, 0xE6, 0x20, 0xD2, 0x4C, 0xD2, 0x0A, 0x57, 0x16, 0xAB, 0x5C,\n     0xDD, 0x65, 0xEC, 0x9C, 0x62, 0xE5, 0x9F, 0x98, 0xBA, 0x38, 0xF2, 0xE9,\n     0x52, 0x40, 0x63, 0x89, 0x94, 0x2E, 0x65, 0x0F, 0x84, 0x91, 0xB3, 0x08,\n     0xD6, 0x62, 0xB9, 0xDB, 0xE6, 0x96, 0x68, 0x34, 0xA7, 0x6D, 0xEA, 0xBC,\n     0xB1, 0xB1, 0x03, 0x27, 0x9E, 0x07, 0x34, 0x96, 0xD0, 0x09, 0x11, 0xC1,\n     0x61, 0xC4, 0x90, 0xAC, 0xF6, 0xA6, 0x82, 0xF2, 0x84, 0xFC, 0x72, 0xCA,\n     0xC1, 0xAE, 0xF1, 0x62, 0x4D, 0x5E, 0x65, 0x5E, 0xF8, 0x47, 0x41, 0x80,\n     0xC6, 0x12, 0x22, 0x99, 0xCD, 0x26, 0xD0, 0xA5, 0xC9, 0x59, 0x4D, 0x97,\n     0xAD, 0x66, 0x75, 0xCF, 0x20, 0xA5, 0xCB, 0xA8, 0xE9, 0x0C, 0x0F, 0x1F,\n     0x99, 0xDB, 0x0C, 0xA8, 0x6C, 0xCC, 0x22, 0x0C, 0x44, 0xD2, 0x19, 0x32,\n     0x6E, 0x08, 0xD5, 0xAC, 0x2E, 0xB6, 0x1A, 0x5D, 0x92, 0xC1, 0xF1, 0x5C,\n     0x4A, 0x65, 0x5A, 0x65, 0xE5, 0x1D, 0x80, 0xCA, 0x16, 0x2E, 0x81, 0x8E,\n     0x84, 0xC6, 0xD5, 0x0E, 0x67, 0x9A, 0x13, 0x12, 0x8A, 0xC7, 0x0B, 0x8C,\n     0xF1, 0xAA, 0x70, 0x71, 0x6E, 0x65, 0x9A, 0xAA, 0xF2, 0x45, 0x40, 0x25,\n     0x20, 0x8E, 0x89, 0x20, 0x91, 0xA1, 0xB5, 0x4D, 0x66, 0x73, 0x69, 0x02,\n     0xB5, 0x40, 0x9C, 0x40, 0x91, 0x78, 0x28, 0x92, 0x34, 0xCF, 0x9C, 0x67,\n     0x0D, 0xA0, 0xB2, 0x32, 0x84, 0x81, 0xA4, 0x07, 0x87, 0xB6, 0x27, 0xA4,\n     0x14, 0x1F, 0x6C, 0xE2, 0xE0, 0x5D, 0x8A, 0xCA, 0xF8, 0x3A, 0x09, 0xC5,\n     0x93, 0x96, 0x76, 0x60, 0x05, 0xA0, 0x73, 0xBB, 0x16, 0x09, 0x0E, 0x26,\n     0x67, 0x95, 0xE6, 0x1A, 0xA9, 0xA7, 0xA9, 0x29, 0x29, 0xC7, 0x28, 0xF1,\n     0xF1, 0x62, 0xCF, 0x5C, 0x9D, 0xE7, 0xE8, 0x06, 0x40, 0x67, 0xA3, 0x9D,\n     0xE1, 0x4D, 0x97, 0x66, 0x1B, 0x0F, 0x9E, 0x76, 0x89, 0x0B, 0x5C, 0xA9,\n     0x86, 0xDC, 0x54, 0x89, 0xC4, 0xE3, 0xE9, 0xAB, 0xDB, 0xEC, 0x03, 0xE8,\n     0xAC, 0x97, 0x32, 0x90, 0xF4, 0x89, 0x6C, 0x0E, 0xFE, 0xA0, 0x38, 0x41,\n     0x2C, 0x56, 0x15, 0xC5, 0xE7, 0xA6, 0x52, 0xE6, 0xEA, 0xEA, 0xFA, 0x5E,\n     0x01, 0x94, 0x96, 0x93, 0x18, 0x91, 0x48, 0x26, 0x89, 0xD4, 0x5E, 0x2C,\n     0xA6, 0xBA, 0xA8, 0xA9, 0x0D, 0xF1, 0x12, 0x3C, 0x45, 0x95, 0xD6, 0xF7,\n     0xD1, 0x52, 0x40, 0xE9, 0x39, 0x9D, 0x3D, 0x5A, 0x3B, 0x44, 0x8A, 0xB6,\n     0x0B, 0xA9, 0xD9, 0x45, 0xD4, 0x5C, 0xBC, 0x48, 0x51, 0xEA, 0x3A, 0x4E,\n     0xE9, 0xEB, 0x5B, 0x05, 0x28, 0x6D, 0x9A, 0xE0, 0xDB, 0x0F, 0x91, 0xE2,\n     0x2E, 0x39, 0x90, 0x89, 0x6C, 0xAA, 0xD9, 0xA8, 0x28, 0xC2, 0x19, 0x4A,\n     0xA8, 0xF8, 0xB4, 0x3B, 0x00, 0xAD, 0x55, 0x99, 0x5A, 0xC6, 0x97, 0xA3,\n     0x9F, 0x9C, 0x8F, 0xAA, 0xB1, 0x67, 0x73, 0x92, 0x0A, 0x1A, 0x92, 0x26,\n     0xDA, 0x2F, 0x27, 0xB9, 0x54, 0x8F, 0x02, 0x5A, 0x3E, 0xC9, 0x7C, 0xFE,\n     0x0F, 0xA3, 0x87, 0x3F, 0xF9, 0x16, 0xD1, 0x72, 0x38, 0x45, 0x38, 0xDC,\n     0x97, 0xF7, 0x9F, 0xFF, 0xCE, 0x9C, 0x9B, 0x76, 0x2F, 0xA0, 0xF6, 0x08,\n     0x5F, 0x1B, 0xDD, 0xDA, 0x7A, 0xF8, 0x43, 0xA9, 0x8E, 0x93, 0x64, 0x2C,\n     0xFA, 0xE5, 0xDB, 0xC3, 0xDF, 0x7F, 0xFF, 0xA1, 0xF8, 0x49, 0x1F, 0x40,\n     0x6D, 0x3D, 0x7F, 0x88, 0x13, 0x17, 0x62, 0x1E, 0xD2, 0x91, 0x0C, 0x8A,\n     0x52, 0xBC, 0xA2, 0xA6, 0xB5, 0x35, 0xA7, 0x64, 0xEE, 0x51, 0x40, 0x6F,\n     0x19, 0x89, 0xD4, 0x8E, 0x4B, 0x4A, 0xA1, 0xE2, 0x38, 0x2E, 0x5C, 0x81,\n     0xCA, 0x83, 0x8F, 0xBB, 0x94, 0x2D, 0x89, 0x7F, 0x6C, 0x1D, 0xA0, 0xB6,\n     0x52, 0x28, 0xD4, 0x0D, 0xE1, 0x4A, 0x0A, 0x70, 0xB8, 0xA4, 0x22, 0x3C,\n     0x45, 0x35, 0x97, 0x92, 0x92, 0xAA, 0x32, 0xBB, 0xD7, 0x00, 0x6A, 0x98,\n     0x10, 0xAD, 0x76, 0x22, 0x36, 0x16, 0x67, 0x28, 0x51, 0x34, 0x88, 0xF0,\n     0x2A, 0x49, 0xAA, 0x38, 0x15, 0xDF, 0x7D, 0xE6, 0x7D, 0x40, 0x6F, 0xBD,\n     0xDD, 0x4E, 0x22, 0x19, 0x62, 0x4B, 0x0C, 0xA2, 0xD2, 0xE3, 0x22, 0x71,\n     0x2A, 0xBE, 0x54, 0xC4, 0xEF, 0xEA, 0x7C, 0x10, 0xD0, 0xF3, 0x4F, 0x74,\n     0x08, 0x49, 0xB1, 0x87, 0x0C, 0x1C, 0xD1, 0x30, 0x5E, 0x24, 0x3A, 0xAE,\n     0x28, 0x9A, 0x38, 0x34, 0x9E, 0x17, 0x08, 0x8B, 0xB0, 0x3A, 0x3A, 0x5A,\n     0xCA, 0xD4, 0x1D, 0x2B, 0x69, 0x38, 0x86, 0x3F, 0xAE, 0x28, 0xC5, 0xE9,\n     0x26, 0xE4, 0xAC, 0x9D, 0xB0, 0x18, 0x98, 0xDB, 0xD2, 0x1D, 0x4C, 0xAD,\n     0x6E, 0x38, 0xA9, 0x41, 0x81, 0x2B, 0x19, 0x8E, 0xE6, 0x4F, 0x96, 0xAB,\n     0x37, 0xC1, 0xA2, 0xF8, 0xBD, 0x84, 0x20, 0x8E, 0xB8, 0xCC, 0xEC, 0x84,\n     0x14, 0x7C, 0xAC, 0x37, 0xAA, 0xBB, 0x42, 0xEE, 0x0B, 0x8B, 0xF4, 0x42,\n     0x06, 0x82, 0x20, 0x21, 0xB8, 0xA4, 0x63, 0xB1, 0xE9, 0x19, 0xC4, 0xC9,\n     0x8E, 0x7D, 0xF7, 0xC1, 0xE2, 0x3C, 0xFD, 0xCC, 0x91, 0xA8, 0x9A, 0x74,\n     0xFE, 0x90, 0xE1, 0x50, 0x34, 0x12, 0x4C, 0x2E, 0xBF, 0x76, 0x7D, 0x3B,\n     0x2C, 0x86, 0xDF, 0x53, 0xF7, 0xD7, 0x63, 0x8F, 0x54, 0x67, 0xA4, 0xF3,\n     0x75, 0x7C, 0x69, 0x71, 0x99, 0xAC, 0x81, 0x66, 0x7B, 0x1C, 0xD0, 0xF3,\n     0xF3, 0x7F, 0xD6, 0x89, 0xED, 0xAF, 0x3F, 0x82, 0xC5, 0x56, 0x27, 0x46,\n     0x65, 0x26, 0x36, 0xB5, 0x47, 0x58, 0xBB, 0x2B, 0x1E, 0x5A, 0x05, 0xE8,\n     0x60, 0x5E, 0xBB, 0xCD, 0x5E, 0x56, 0x93, 0x11, 0x85, 0xED, 0xCF, 0xC1,\n     0x62, 0x5B, 0x32, 0x32, 0xDA, 0xA4, 0x24, 0xA4, 0x70, 0xBE, 0xF9, 0xBA,\n     0xDA, 0x17, 0x50, 0xF1, 0x7F, 0x98, 0xEE, 0x0D, 0xAD, 0xCA, 0xF1, 0x46,\n     0xD6, 0x38, 0xB1, 0x39, 0xCE, 0x28, 0x6C, 0x8B, 0xC5, 0x91, 0x38, 0x9D,\n     0x1C, 0x61, 0x33, 0xB9, 0x2B, 0xF6, 0x62, 0x00, 0x85, 0x37, 0xC9, 0x91,\n     0x76, 0xBD, 0xB7, 0x8A, 0x1D, 0x4A, 0x2F, 0xCB, 0x68, 0xFD, 0xB8, 0x1F,\n     0x9B, 0xD3, 0xD8, 0xB6, 0xFF, 0xEA, 0x7E, 0xCB, 0x7C, 0xB9, 0xAD, 0xBC,\n     0x63, 0x2F, 0x06, 0x6E, 0x6D, 0x0B, 0x33, 0x32, 0x84, 0x19, 0xC3, 0x0E,\n     0x0B, 0x0B, 0x9D, 0xA6, 0xC7, 0xB4, 0x8E, 0xC6, 0x7C, 0x6C, 0xF9, 0xAC,\n     0xED, 0xEC, 0x55, 0x6F, 0x19, 0x6D, 0x52, 0xDE, 0xD3, 0xF3, 0x16, 0xDC,\n     0xD2, 0xDB, 0x8D, 0xB5, 0xB5, 0x8E, 0x98, 0xA8, 0xE0, 0x01, 0x69, 0x84,\n     0x8E, 0xA7, 0x8F, 0xE9, 0xB7, 0xB4, 0x94, 0xD1, 0xF3, 0xCF, 0x26, 0x3B,\n     0x2D, 0x4C, 0x9A, 0x95, 0x3B, 0xB9, 0xEF, 0x01, 0xB8, 0x85, 0x5D, 0x37,\n     0x4D, 0x99, 0x99, 0xCC, 0x28, 0x67, 0x55, 0xB0, 0x37, 0x8C, 0xA8, 0x9F,\n     0xF7, 0x36, 0x32, 0xE9, 0x04, 0x9A, 0x72, 0xBE, 0x0C, 0xDB, 0x5F, 0x66,\n     0x92, 0xC9, 0x9B, 0xBF, 0xE8, 0xD8, 0x0A, 0x7F, 0xC1, 0x3F, 0x60, 0x0B,\n     0x8F, 0x97, 0x4F, 0x23, 0x27, 0x1E, 0xA9, 0xAE, 0x76, 0xB2, 0xAB, 0x08,\n     0x52, 0x13, 0xC3, 0xCB, 0xD3, 0xEB, 0x09, 0x27, 0x1B, 0xB1, 0x87, 0x47,\n     0xEB, 0x43, 0xA5, 0x44, 0xDA, 0x15, 0x35, 0x6B, 0x05, 0x2C, 0x68, 0x39,\n     0x97, 0x40, 0x94, 0x75, 0xC7, 0xCA, 0xB4, 0x8C, 0x2A, 0x4B, 0x35, 0x96,\n     0x1D, 0x1C, 0x46, 0xE0, 0xF2, 0x78, 0x3C, 0xA1, 0x30, 0xC2, 0x12, 0x53,\n     0x5F, 0x3F, 0x5A, 0xEF, 0x64, 0x48, 0xC9, 0x5F, 0xD8, 0xDC, 0xEF, 0x06,\n     0xC2, 0x42, 0xF6, 0xCB, 0x84, 0x0C, 0xBD, 0x52, 0x2E, 0x27, 0xEA, 0x4D,\n     0xCC, 0x93, 0x85, 0x6C, 0x13, 0x81, 0x5B, 0x2B, 0x97, 0x0B, 0x42, 0x78,\n     0x8D, 0x16, 0x27, 0x16, 0x7B, 0xAE, 0x25, 0x07, 0xD1, 0x73, 0x0A, 0xAF,\n     0x37, 0xFF, 0xB8, 0xC7, 0x07, 0xFE, 0xDC, 0x76, 0x1A, 0x7B, 0x40, 0x49,\n     0x53, 0x12, 0x68, 0x82, 0x6E, 0x81, 0x40, 0xCF, 0x60, 0x0F, 0x0C, 0x10,\n     0x05, 0x72, 0xB7, 0xDA, 0x78, 0xD2, 0x5B, 0x16, 0x13, 0xD3, 0xF2, 0xF3,\n     0x37, 0xF5, 0x8D, 0xD3, 0xC9, 0xA6, 0x0A, 0x56, 0xF8, 0x66, 0x58, 0xC0,\n     0x7D, 0xB7, 0xF3, 0x22, 0x0A, 0x4F, 0x36, 0x0B, 0x9A, 0x95, 0xB2, 0x58,\n     0x1D, 0x23, 0xB1, 0x2A, 0x8C, 0x58, 0xCB, 0xCD, 0x67, 0x69, 0xE2, 0x6D,\n     0x85, 0x16, 0x67, 0xCE, 0xF9, 0xAF, 0xBE, 0xFA, 0xAE, 0xA5, 0x30, 0x59,\n     0x78, 0x65, 0xFC, 0xE8, 0xD1, 0x40, 0x58, 0xC0, 0x72, 0xDC, 0xB0, 0x01,\n     0x61, 0xDE, 0xA4, 0x65, 0x11, 0x9B, 0x65, 0x61, 0x64, 0x72, 0x24, 0x41,\n     0x20, 0x10, 0x58, 0x3B, 0x59, 0xBC, 0xCF, 0xDA, 0xCE, 0x9E, 0xFB, 0xFA,\n     0x9B, 0xCF, 0x7F, 0x3A, 0x75, 0x8E, 0x99, 0x6C, 0x15, 0x1D, 0x58, 0x03,\n     0x0B, 0x59, 0xFA, 0xFA, 0xDD, 0xEF, 0x5C, 0x38, 0xDB, 0x16, 0x11, 0x41,\n     0xCC, 0x8F, 0xBB, 0xE4, 0x40, 0x1C, 0x64, 0x59, 0xF7, 0xA4, 0xBB, 0xE3,\n     0xC6, 0xE7, 0xA7, 0x4E, 0x9D, 0xFA, 0xE0, 0x37, 0x5F, 0x9F, 0x3B, 0x9F,\n     0xC8, 0x15, 0xCD, 0xBD, 0x1A, 0x04, 0x7F, 0x25, 0x28, 0x68, 0xC3, 0x86,\n     0x7B, 0x97, 0xAE, 0x5B, 0xB7, 0xED, 0x9E, 0xAD, 0xCB, 0x96, 0x2D, 0x0B,\n     0x08, 0xD8, 0x7A, 0xCF, 0xBA, 0x15, 0x6B, 0xD7, 0xAE, 0x7D, 0x70, 0xED,\n     0xEE, 0xDD, 0xBB, 0xD7, 0xEC, 0xF0, 0x5D, 0x7D, 0xF7, 0xB6, 0x20, 0xB8,\n     0x15, 0x1F, 0xF8, 0x23, 0x1F, 0xF8, 0xDF, 0xBF, 0xD8, 0xAF, 0xCE, 0xC3,\n     0xA2, 0x1B, 0x17, 0x18, 0xB4, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,\n     0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82\n};\n"
  },
  {
    "path": "src/samples/groot/groot.rc",
    "content": "#ifdef RC_INVOKED\n#ifndef _INC_WINDOWS\n#define _INC_WINDOWS\n#include \"winres.h\"           // extract from windows header\n#endif\n#endif\n\n\n1 RT_MANIFEST \"manifest.xml\"\n101 ICON \"groot.ico\"\n\n#define file_description \"Groot\"\n#define original_file_name \"groot.exe\"\n#define product_name \"groot\"\n#define company_name \"https://leok7v.github.io/\"\n#define copyright \"Copyright (C) 2021-2024 Dmitry `Leo` Kuznetsov. All rights reserved.\"\n\n#include \"..\\..\\inc\\rt\\version.rc.in\"\n\n#include \"i18n.h\"\n\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\n#pragma code_page(65001)\n"
  },
  {
    "path": "src/samples/groot/rocket.h",
    "content": "#pragma once\n\nstatic unsigned char rocket[] = { // rocket.jpg file 64x72 rgb\n     0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01,\n     0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x84,\n     0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06,\n     0x06, 0x05, 0x06, 0x06, 0x08, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0C, 0x09,\n     0x09, 0x09, 0x09, 0x09, 0x0C, 0x13, 0x0C, 0x0E, 0x0C, 0x0C, 0x0E, 0x0C,\n     0x13, 0x11, 0x14, 0x10, 0x0F, 0x10, 0x14, 0x11, 0x1E, 0x17, 0x15, 0x15,\n     0x17, 0x1E, 0x22, 0x1D, 0x1B, 0x1D, 0x22, 0x2A, 0x25, 0x25, 0x2A, 0x34,\n     0x32, 0x34, 0x44, 0x44, 0x5C, 0x01, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,\n     0x04, 0x04, 0x04, 0x04, 0x06, 0x06, 0x05, 0x06, 0x06, 0x08, 0x07, 0x07,\n     0x07, 0x07, 0x08, 0x0C, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0C, 0x13, 0x0C,\n     0x0E, 0x0C, 0x0C, 0x0E, 0x0C, 0x13, 0x11, 0x14, 0x10, 0x0F, 0x10, 0x14,\n     0x11, 0x1E, 0x17, 0x15, 0x15, 0x17, 0x1E, 0x22, 0x1D, 0x1B, 0x1D, 0x22,\n     0x2A, 0x25, 0x25, 0x2A, 0x34, 0x32, 0x34, 0x44, 0x44, 0x5C, 0xFF, 0xC2,\n     0x00, 0x11, 0x08, 0x00, 0x48, 0x00, 0x40, 0x03, 0x01, 0x22, 0x00, 0x02,\n     0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x1C, 0x00, 0x00, 0x03,\n     0x00, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n     0x00, 0x00, 0x05, 0x06, 0x07, 0x04, 0x08, 0x01, 0x02, 0x03, 0x00, 0xFF,\n     0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x2E, 0xDC,\n     0x73, 0xB0, 0xA0, 0x7E, 0x07, 0x33, 0xBE, 0xA4, 0xE0, 0xE6, 0xD4, 0x03,\n     0xC8, 0xD7, 0x69, 0x36, 0x02, 0x73, 0xA9, 0x5B, 0xFA, 0xE2, 0x16, 0xD3,\n     0x4E, 0x6B, 0x32, 0x5E, 0xB9, 0x81, 0x26, 0x3B, 0x6C, 0xF3, 0x39, 0xF0,\n     0x8F, 0x38, 0xA9, 0xCD, 0xFD, 0xDB, 0x69, 0xD2, 0x61, 0x8C, 0x65, 0x10,\n     0xAB, 0x0B, 0xD9, 0xF4, 0x98, 0x83, 0x58, 0x00, 0x7B, 0x0E, 0xAF, 0x88,\n     0x6C, 0x28, 0xA4, 0xBE, 0xF7, 0x54, 0x35, 0xB7, 0xF1, 0x63, 0x82, 0xFF,\n     0x00, 0xFF, 0xC4, 0x00, 0x19, 0x01, 0x00, 0x02, 0x03, 0x01, 0x00, 0x00,\n     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04,\n     0x01, 0x02, 0x05, 0x00, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x02, 0x10, 0x00,\n     0x00, 0x00, 0x99, 0x4C, 0x8C, 0x05, 0xB5, 0xC9, 0xC6, 0x04, 0x5D, 0x63,\n     0x06, 0xD9, 0x9A, 0x1F, 0xFF, 0xC4, 0x00, 0x19, 0x01, 0x00, 0x02, 0x03,\n     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n     0x00, 0x03, 0x04, 0x00, 0x01, 0x02, 0x05, 0xFF, 0xDA, 0x00, 0x08, 0x01,\n     0x03, 0x10, 0x00, 0x00, 0x00, 0x90, 0xE3, 0xC9, 0x53, 0x63, 0x14, 0x23,\n     0x68, 0x6D, 0x2E, 0x6B, 0xEC, 0xF1, 0xFF, 0x00, 0xFF, 0xC4, 0x00, 0x25,\n     0x10, 0x00, 0x02, 0x03, 0x01, 0x00, 0x02, 0x03, 0x00, 0x02, 0x02, 0x03,\n     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x01, 0x02, 0x05, 0x06, 0x00,\n     0x13, 0x07, 0x11, 0x12, 0x14, 0x21, 0x10, 0x31, 0x16, 0x22, 0x41, 0xFF,\n     0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x01, 0x12, 0x00, 0xFA, 0xFF, 0x00,\n     0x3F, 0xEB, 0xCC, 0x61, 0x0B, 0xA5, 0xE4, 0x16, 0x14, 0xB5, 0x7B, 0x30,\n     0x01, 0x56, 0x2D, 0x57, 0x71, 0xF5, 0x77, 0x55, 0x4D, 0x64, 0x12, 0xBB,\n     0x3A, 0x34, 0x99, 0x50, 0xC0, 0x0F, 0xC5, 0xF3, 0x98, 0xB8, 0x9A, 0xEA,\n     0x9C, 0x90, 0xCD, 0xAB, 0xFA, 0x85, 0x3B, 0x1B, 0x73, 0x81, 0xCD, 0x57,\n     0x3F, 0x28, 0x4B, 0x0C, 0xE2, 0x35, 0x6D, 0x5A, 0xF9, 0x92, 0x4E, 0x6B,\n     0x1B, 0xE3, 0x9C, 0x76, 0x8F, 0xCE, 0xA7, 0xA0, 0xC6, 0x81, 0x59, 0x87,\n     0x0C, 0x7E, 0x45, 0x3D, 0xE6, 0xA9, 0xFF, 0x00, 0x09, 0x92, 0x96, 0xE5,\n     0x89, 0xBF, 0xF0, 0x18, 0xF8, 0xF7, 0xA4, 0x56, 0xB3, 0x66, 0x02, 0x0A,\n     0x44, 0x5B, 0xF3, 0xE7, 0x3B, 0x9D, 0xD2, 0x73, 0x5A, 0x8A, 0xE9, 0x01,\n     0x48, 0x61, 0x78, 0xB5, 0x61, 0x8A, 0x3F, 0xAF, 0x89, 0x8B, 0xAE, 0x0D,\n     0x13, 0xEA, 0xC0, 0x26, 0xB3, 0x13, 0xEB, 0xF9, 0x2A, 0x9A, 0x44, 0xC3,\n     0xC9, 0xDD, 0x57, 0x66, 0xC7, 0xCA, 0xD0, 0x29, 0x2B, 0x00, 0x02, 0xC4,\n     0x75, 0x17, 0x6F, 0x02, 0xFB, 0x95, 0x66, 0x22, 0x6F, 0xE7, 0x22, 0xC9,\n     0x75, 0x32, 0x35, 0xB9, 0xC9, 0xBC, 0x7E, 0xA8, 0x3B, 0x36, 0xA7, 0x9C,\n     0x57, 0xC4, 0x3B, 0x19, 0xAB, 0x5B, 0xA0, 0xD8, 0x75, 0x64, 0x2F, 0x41,\n     0xFB, 0x56, 0x58, 0x39, 0x42, 0x7C, 0xB9, 0x2B, 0x51, 0x69, 0x31, 0x74,\n     0xEE, 0xC0, 0x83, 0x5D, 0x6C, 0x4D, 0x45, 0x7F, 0x96, 0xF2, 0x30, 0x75,\n     0x60, 0x70, 0x41, 0xFE, 0xD4, 0xCF, 0x67, 0x4F, 0x46, 0x8B, 0x8A, 0xB6,\n     0x29, 0x62, 0x67, 0xF5, 0xE7, 0x5F, 0xD0, 0x15, 0xB4, 0xF1, 0x78, 0xE0,\n     0x50, 0x90, 0x92, 0x20, 0x2B, 0x04, 0x37, 0x1E, 0x1B, 0x9B, 0x78, 0xD8,\n     0xF7, 0xFF, 0x00, 0xAD, 0x74, 0x54, 0xB0, 0xA6, 0xB1, 0xF7, 0xFD, 0x79,\n     0xF1, 0x87, 0x29, 0x44, 0x79, 0x47, 0x7A, 0x61, 0x88, 0x12, 0xEF, 0xE6,\n     0x66, 0xA6, 0x65, 0x1D, 0x6D, 0x8D, 0x77, 0x04, 0xD5, 0x98, 0x73, 0x65,\n     0xB3, 0xD2, 0x02, 0x6D, 0x1F, 0x8F, 0x3A, 0x88, 0x7F, 0x33, 0x1D, 0x1D,\n     0x01, 0x46, 0xF6, 0x72, 0xD6, 0x81, 0xAA, 0xA7, 0x7B, 0xD5, 0xF3, 0x10,\n     0xA5, 0x47, 0xB5, 0x25, 0xB2, 0xFE, 0xC0, 0x9D, 0x1C, 0x2C, 0xF4, 0x3A,\n     0xA7, 0x5F, 0xD6, 0x59, 0xEB, 0x25, 0xA1, 0x16, 0x93, 0x7F, 0x13, 0xE4,\n     0x44, 0xA8, 0xB9, 0x71, 0x36, 0x41, 0x48, 0x9A, 0xC1, 0x7D, 0x44, 0xF3,\n     0x1D, 0x37, 0x01, 0xA6, 0x86, 0x82, 0x71, 0x33, 0x60, 0x1C, 0x6D, 0x06,\n     0xF1, 0x31, 0xF7, 0x1F, 0x7F, 0xEB, 0xCE, 0x1B, 0xB6, 0xC3, 0x7B, 0x8A,\n     0x1F, 0x37, 0x4B, 0xDD, 0x77, 0xE9, 0x6F, 0xDF, 0x9F, 0x19, 0x6E, 0xF2,\n     0x19, 0x7D, 0x48, 0x0F, 0xA0, 0x21, 0xAD, 0xA2, 0x01, 0x16, 0xC1, 0x61,\n     0x55, 0xB4, 0xF0, 0xBE, 0x4C, 0xCE, 0x77, 0x50, 0xB4, 0x90, 0x2D, 0xAA,\n     0x7D, 0x03, 0x3B, 0xF2, 0xDE, 0xFF, 0x00, 0x0F, 0xD1, 0x75, 0x84, 0x63,\n     0x3F, 0x36, 0x82, 0x62, 0xC1, 0xAF, 0xF2, 0x7C, 0xCF, 0xDB, 0x06, 0x0E,\n     0x99, 0x0C, 0x21, 0x48, 0x43, 0x68, 0xB5, 0x64, 0x4F, 0xBA, 0x3D, 0x6E,\n     0x35, 0xA8, 0xB0, 0x84, 0x31, 0x10, 0x46, 0x9A, 0xDB, 0x9B, 0x2E, 0x8B,\n     0x43, 0x72, 0x57, 0x2C, 0x58, 0x82, 0x56, 0xF7, 0xF4, 0x78, 0x06, 0x4E,\n     0xA9, 0x20, 0xA0, 0x24, 0xD2, 0xD1, 0xFF, 0x00, 0xAA, 0x74, 0xD5, 0x25,\n     0xD4, 0xAB, 0xB1, 0x7A, 0x40, 0x6F, 0x5B, 0xC1, 0x74, 0x3A, 0x8D, 0xF2,\n     0xA9, 0x0E, 0xE5, 0x6E, 0x50, 0xD3, 0x33, 0xF8, 0x9B, 0xB7, 0x97, 0xAC,\n     0xE9, 0x9C, 0x7B, 0x40, 0xA4, 0xB9, 0xFE, 0xA4, 0x97, 0xB6, 0x49, 0x41,\n     0x46, 0x26, 0xAC, 0x8A, 0x84, 0xA1, 0x29, 0x23, 0x88, 0x1D, 0xEC, 0xD6,\n     0x6B, 0xB9, 0x22, 0x21, 0xE6, 0xA3, 0x27, 0xB6, 0x95, 0xE2, 0xD5, 0x2B,\n     0xC6, 0x60, 0xCB, 0x3A, 0xB0, 0xCC, 0x00, 0x56, 0x6E, 0x1C, 0xF5, 0xA5,\n     0xA6, 0x46, 0x1A, 0x82, 0xE6, 0xBD, 0xA6, 0x22, 0xA2, 0xAF, 0x2F, 0xB5,\n     0x54, 0x84, 0x4A, 0x71, 0x68, 0x15, 0x7A, 0xDB, 0xEA, 0xF2, 0xD6, 0x46,\n     0x26, 0x95, 0xD8, 0xAA, 0x08, 0x97, 0x39, 0xA1, 0x4C, 0xC9, 0x13, 0xE7,\n     0x31, 0x9F, 0x5F, 0x44, 0x00, 0xAC, 0x43, 0x0B, 0x12, 0x4B, 0x13, 0x45,\n     0x1E, 0xC5, 0x9A, 0x44, 0xB7, 0x9E, 0x4F, 0x55, 0xEF, 0x11, 0x0C, 0xBB,\n     0x94, 0x9E, 0x6E, 0x8E, 0xF0, 0xA5, 0x91, 0xDE, 0x17, 0x62, 0xC1, 0x52,\n     0xAF, 0xE7, 0x29, 0x91, 0x87, 0x91, 0xBD, 0x97, 0x05, 0xB9, 0x5E, 0xAF,\n     0xE4, 0xD1, 0x8A, 0xFC, 0xB2, 0xE5, 0x12, 0xFC, 0x0D, 0x65, 0x4B, 0x4B,\n     0x7D, 0x8B, 0x99, 0x29, 0x45, 0xB6, 0x85, 0x85, 0xFB, 0xFD, 0x7B, 0x6B,\n     0x33, 0xE6, 0x37, 0xC8, 0xDA, 0x99, 0xAB, 0x91, 0x66, 0x6D, 0x24, 0x08,\n     0xAF, 0x6A, 0xFA, 0xBB, 0x40, 0x81, 0xAB, 0x2F, 0xD3, 0xE1, 0x86, 0x44,\n     0x61, 0xCD, 0xCC, 0x4B, 0x23, 0xAE, 0x3B, 0xE4, 0x11, 0x8C, 0xC2, 0x8A,\n     0x08, 0x59, 0x8A, 0xDA, 0x9C, 0xF0, 0x59, 0x70, 0xA5, 0xBC, 0xAD, 0x14,\n     0xAC, 0xD3, 0xEA, 0xC3, 0xD4, 0xE4, 0x5D, 0xC9, 0xD6, 0x2B, 0x8C, 0x08,\n     0x2D, 0x4D, 0xC0, 0x56, 0xCA, 0xA0, 0xDE, 0xCF, 0x2E, 0x36, 0x87, 0x33,\n     0xEF, 0xFE, 0xC3, 0xF9, 0x79, 0x09, 0x40, 0xAB, 0x21, 0xA3, 0x07, 0x60,\n     0x77, 0x38, 0x85, 0x52, 0x16, 0xA1, 0xE2, 0x6E, 0x20, 0xEF, 0x01, 0x93,\n     0x44, 0xFA, 0xC3, 0x4B, 0xDE, 0x7C, 0x65, 0xD7, 0x0F, 0x9F, 0x62, 0x5B,\n     0x16, 0x92, 0x32, 0x82, 0x96, 0x5C, 0x6F, 0x34, 0xF0, 0x11, 0xB5, 0xAE,\n     0x69, 0x90, 0x5A, 0xB5, 0x9A, 0x53, 0x28, 0x4F, 0xA2, 0x40, 0x18, 0x20,\n     0x83, 0x0E, 0x7E, 0xCD, 0xE9, 0xC2, 0x7C, 0x17, 0x68, 0xFE, 0xC8, 0xB8,\n     0x6F, 0x35, 0x99, 0xAD, 0x1E, 0xE9, 0x1B, 0x2B, 0x2D, 0x92, 0x5D, 0x9B,\n     0x7B, 0xAF, 0x65, 0x3D, 0x1B, 0x3C, 0xD0, 0x54, 0x3D, 0x74, 0x59, 0x64,\n     0x45, 0xB5, 0xE2, 0x2E, 0x45, 0xE1, 0x24, 0x95, 0x5C, 0x47, 0x0D, 0x62,\n     0xEC, 0x47, 0xBC, 0x71, 0x3F, 0xFF, 0xC4, 0x00, 0x2B, 0x10, 0x01, 0x00,\n     0x02, 0x02, 0x02, 0x02, 0x01, 0x03, 0x02, 0x06, 0x03, 0x00, 0x00, 0x00,\n     0x00, 0x00, 0x01, 0x02, 0x11, 0x00, 0x03, 0x12, 0x21, 0x31, 0x41, 0x04,\n     0x10, 0x13, 0x22, 0x51, 0x61, 0x20, 0x32, 0x62, 0x71, 0x72, 0x91, 0x42,\n     0x81, 0x82, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01, 0x00, 0x13, 0x3F, 0x00,\n     0xFE, 0x06, 0x4A, 0x21, 0x60, 0xD7, 0x8E, 0x93, 0x22, 0x9C, 0x98, 0xC2,\n     0xC8, 0xCA, 0x94, 0x3F, 0x1F, 0x0B, 0x9F, 0x0A, 0x51, 0x9E, 0xDA, 0xFE,\n     0xBD, 0xA8, 0xC6, 0x29, 0xFD, 0x24, 0xB3, 0x48, 0xCE, 0x66, 0xB6, 0x2B,\n     0x23, 0x66, 0xC9, 0x5A, 0xAA, 0x8D, 0x2F, 0xD3, 0x74, 0x2F, 0x6C, 0x63,\n     0x0D, 0x8C, 0x42, 0x12, 0x3B, 0x8D, 0x19, 0xF2, 0x67, 0x1F, 0xBD, 0x10,\n     0xEE, 0x4E, 0xB9, 0x74, 0x4E, 0x26, 0x3B, 0x63, 0x9A, 0x36, 0xC7, 0x69,\n     0x2D, 0x7E, 0xC9, 0x46, 0x2D, 0x95, 0x9A, 0x75, 0x3B, 0x77, 0x4D, 0x29,\n     0x8D, 0x03, 0x10, 0xEB, 0xA6, 0xD0, 0xC3, 0xE3, 0x9F, 0x1A, 0x7A, 0xA5,\n     0x06, 0xF8, 0xCF, 0xF3, 0x9B, 0x2F, 0x3D, 0x23, 0x58, 0x1D, 0x3D, 0x32,\n     0x88, 0xFE, 0xF5, 0x17, 0xE8, 0x96, 0x33, 0xE8, 0xD9, 0xAB, 0xFF, 0x00,\n     0x45, 0x27, 0xEE, 0x64, 0xB7, 0x87, 0xC8, 0x57, 0xA4, 0x89, 0xE4, 0x53,\n     0x36, 0x4C, 0x88, 0xC3, 0xE2, 0x4D, 0x36, 0xB7, 0x7D, 0x74, 0x5D, 0x99,\n     0xAD, 0x91, 0xAE, 0x45, 0xF6, 0x45, 0xB1, 0x5A, 0x7C, 0x65, 0xBE, 0xBB,\n     0x65, 0x25, 0xF0, 0x1E, 0xDC, 0xD9, 0xD1, 0xBF, 0x7E, 0xC8, 0x83, 0x38,\n     0x07, 0x46, 0xA8, 0x90, 0xA8, 0xE7, 0x56, 0xCA, 0x01, 0xB6, 0x31, 0x17,\n     0xA2, 0x4B, 0x0A, 0xFA, 0x6C, 0x63, 0x19, 0x6A, 0x2F, 0xD7, 0x29, 0x64,\n     0x62, 0x9C, 0x59, 0xCB, 0xCC, 0x18, 0xC8, 0x2D, 0xB0, 0x02, 0x2E, 0x47,\n     0xE4, 0x45, 0xDA, 0x7E, 0x0C, 0xE6, 0xC9, 0x1A, 0x24, 0xE6, 0xFD, 0x51,\n     0x94, 0x4B, 0x9B, 0xCA, 0x30, 0x65, 0x14, 0xA4, 0x3B, 0x72, 0x31, 0x89,\n     0xA7, 0x6B, 0xE6, 0x40, 0xB2, 0x10, 0xFF, 0x00, 0x6B, 0x9E, 0x2C, 0xD9,\n     0xF9, 0x10, 0xEB, 0xD5, 0x92, 0xB3, 0x14, 0x2F, 0x59, 0x3E, 0x3B, 0x63,\n     0x2F, 0x71, 0xE5, 0x77, 0x17, 0xC2, 0x7D, 0x21, 0x3F, 0xE6, 0x68, 0x08,\n     0xD4, 0x89, 0xE6, 0xFE, 0x2F, 0xE7, 0x33, 0x88, 0xDC, 0x00, 0x0F, 0xDE,\n     0x59, 0xC7, 0x59, 0xB3, 0x7C, 0x36, 0x4D, 0x99, 0x09, 0x6D, 0x11, 0x4F,\n     0x48, 0xB8, 0xEB, 0x74, 0x33, 0xDA, 0xF9, 0xD9, 0x73, 0xA1, 0xBF, 0xF1,\n     0xC9, 0x6B, 0x8A, 0xCA, 0x37, 0x55, 0xCA, 0x8B, 0x7D, 0xD2, 0x06, 0x08,\n     0x70, 0xDB, 0xA2, 0x46, 0xD2, 0xC7, 0xC3, 0x20, 0x42, 0xB3, 0xB5, 0xD9,\n     0xAA, 0xDE, 0x71, 0x85, 0x3D, 0x25, 0xA9, 0x5F, 0x41, 0xA7, 0x35, 0x35,\n     0x32, 0x63, 0x6C, 0xAF, 0xDA, 0xB9, 0x2D, 0x1A, 0xCD, 0x91, 0x8B, 0xEC,\n     0x6A, 0xF2, 0x77, 0x29, 0x4B, 0xAF, 0x37, 0x93, 0x04, 0x14, 0xA1, 0xEF,\n     0xA3, 0xFB, 0xFA, 0xCD, 0x6C, 0x48, 0x29, 0x1A, 0x84, 0xA7, 0xD5, 0xC9,\n     0xA5, 0x10, 0x4B, 0xBC, 0xD9, 0x17, 0xF3, 0xD6, 0xDC, 0x24, 0xD0, 0xF7,\n     0x56, 0x09, 0x90, 0xB6, 0x53, 0x92, 0xF5, 0x1E, 0xBB, 0xCE, 0x1B, 0xC5,\n     0x2E, 0xAE, 0x2F, 0xDC, 0xE4, 0xE6, 0xCD, 0xAE, 0xE4, 0x8D, 0x0F, 0x2D,\n     0x4A, 0x1C, 0x8B, 0x7B, 0x1E, 0xC3, 0x06, 0x9A, 0xE0, 0xDC, 0xA3, 0xE9,\n     0x94, 0x43, 0x90, 0x64, 0x1A, 0xFC, 0x2C, 0x3B, 0x8A, 0x7B, 0x32, 0x23,\n     0x27, 0x65, 0xCA, 0xC9, 0x74, 0x94, 0x11, 0xF3, 0x79, 0xB7, 0x6C, 0x9D,\n     0x51, 0xDB, 0x03, 0xBE, 0x21, 0x4D, 0xB1, 0x9F, 0xB7, 0x3E, 0x3C, 0x4D,\n     0x63, 0xFE, 0x6F, 0x72, 0x9D, 0x55, 0x9C, 0x96, 0x9C, 0x81, 0x72, 0xA1,\n     0xB6, 0x8F, 0x79, 0xF6, 0xE3, 0x18, 0x49, 0x9C, 0x9B, 0x26, 0x87, 0xFD,\n     0x01, 0x82, 0xA3, 0x3B, 0x55, 0xA7, 0xD5, 0x1D, 0xE2, 0x32, 0xDB, 0xA6,\n     0x6C, 0xBB, 0xEA, 0xED, 0x05, 0x29, 0xC7, 0x63, 0xB2, 0x36, 0x2D, 0x59,\n     0x08, 0xAB, 0x1F, 0xD4, 0xEB, 0x23, 0x37, 0xED, 0x9F, 0x1F, 0x61, 0xC3,\n     0x8C, 0x25, 0x57, 0xCC, 0x11, 0x14, 0xE9, 0xF5, 0x9B, 0x50, 0x94, 0x6A,\n     0xC9, 0xEA, 0x91, 0xE3, 0x9D, 0x7F, 0xBA, 0x33, 0x54, 0xB8, 0xBB, 0x0E,\n     0x0C, 0xB8, 0xAF, 0xA8, 0x9F, 0xF2, 0x7C, 0xD5, 0xE0, 0xD2, 0xF5, 0x55,\n     0x8C, 0x23, 0x1E, 0xC9, 0xC9, 0x59, 0x35, 0xD8, 0xDD, 0xC9, 0xF6, 0xD6,\n     0x78, 0x48, 0x27, 0x19, 0x45, 0x0E, 0x9C, 0x92, 0x44, 0x4E, 0x2C, 0x59,\n     0x07, 0xA6, 0x9A, 0xBC, 0x3A, 0x9C, 0x1F, 0xD1, 0xBF, 0x54, 0x7A, 0xF3,\n     0x86, 0x99, 0xC7, 0x94, 0x45, 0x63, 0x2E, 0x52, 0x58, 0xA0, 0x75, 0xD2,\n     0x2D, 0xE1, 0x16, 0xE0, 0xEB, 0x8C, 0x64, 0x46, 0xD6, 0xA4, 0x4E, 0x92,\n     0xCE, 0xC4, 0x71, 0x69, 0x9C, 0xF8, 0xCD, 0x8F, 0xF7, 0xEA, 0x46, 0x7F,\n     0xFF, 0xC4, 0x00, 0x23, 0x11, 0x00, 0x02, 0x02, 0x02, 0x00, 0x06, 0x03,\n     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,\n     0x11, 0x00, 0x21, 0x04, 0x12, 0x22, 0x32, 0x41, 0x51, 0x13, 0x31, 0x61,\n     0x71, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x02, 0x01, 0x01, 0x3F, 0x00, 0x66,\n     0xE4, 0xAD, 0x13, 0x66, 0xB0, 0x32, 0x9A, 0xDE, 0x38, 0x78, 0xA4, 0xB8,\n     0x85, 0x93, 0x65, 0x7D, 0x5F, 0x91, 0x90, 0x2C, 0xCE, 0x12, 0x5E, 0x22,\n     0x4E, 0xA0, 0xDD, 0xAB, 0xA0, 0x32, 0x4A, 0x0B, 0x9C, 0xA4, 0x32, 0x97,\n     0x92, 0xC9, 0x3A, 0xA1, 0x95, 0xAF, 0xDC, 0x8D, 0x83, 0xC3, 0x7D, 0xBF,\n     0x60, 0xFF, 0x00, 0x46, 0x10, 0x1C, 0x51, 0xC5, 0x22, 0x26, 0x00, 0x83,\n     0x47, 0x57, 0x84, 0xFA, 0xC5, 0x4A, 0x12, 0xFD, 0x6C, 0xF3, 0x51, 0xC9,\n     0x1A, 0x41, 0x5F, 0x18, 0x07, 0x23, 0x75, 0x92, 0x26, 0x24, 0x78, 0x37,\n     0xF9, 0x91, 0x71, 0x1C, 0x3A, 0x96, 0x43, 0x20, 0xBD, 0x6A, 0xEF, 0x10,\n     0x9A, 0x70, 0xDD, 0x44, 0x13, 0x47, 0xD8, 0xF1, 0x93, 0xE9, 0xD0, 0xE3,\n     0xB5, 0x70, 0xF3, 0x1A, 0x07, 0x55, 0x90, 0xA2, 0x16, 0x9A, 0xD4, 0x1E,\n     0x81, 0x90, 0x92, 0x62, 0x8A, 0xC9, 0xED, 0x51, 0x9F, 0xFF, 0xC4, 0x00,\n     0x23, 0x11, 0x00, 0x02, 0x02, 0x02, 0x01, 0x04, 0x03, 0x01, 0x01, 0x00,\n     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x11, 0x03, 0x21,\n     0x04, 0x13, 0x31, 0x32, 0x41, 0x12, 0x22, 0x51, 0x71, 0xB1, 0xFF, 0xDA,\n     0x00, 0x08, 0x01, 0x03, 0x01, 0x01, 0x3F, 0x00, 0x55, 0x2E, 0x68, 0x42,\n     0xA4, 0x12, 0x0C, 0x01, 0x32, 0x63, 0xA7, 0xEC, 0x28, 0x18, 0xE5, 0x14,\n     0x95, 0xC4, 0xBA, 0xAE, 0xE7, 0xB9, 0x98, 0xEC, 0xB8, 0xA9, 0x7F, 0x34,\n     0x75, 0x44, 0x0B, 0x42, 0xCD, 0x98, 0x0D, 0x1D, 0xF8, 0xCC, 0x83, 0xE3,\n     0x93, 0xF4, 0x1A, 0x31, 0x49, 0x53, 0x62, 0x3A, 0x36, 0x40, 0xC4, 0x55,\n     0x8D, 0xD4, 0x55, 0x15, 0x44, 0xFF, 0x00, 0x63, 0xB6, 0xB1, 0x8D, 0xFD,\n     0x45, 0x45, 0x5C, 0x66, 0xFA, 0x84, 0xCC, 0x88, 0xE9, 0x91, 0x42, 0x35,\n     0x82, 0x45, 0x4C, 0x9C, 0x5E, 0x55, 0x07, 0xE8, 0xB0, 0x04, 0x7B, 0x15,\n     0x7B, 0x8C, 0x3C, 0x6B, 0x57, 0xEB, 0xF0, 0xCC, 0x7B, 0x56, 0x13, 0x0A,\n     0x03, 0xCB, 0xE3, 0x28, 0x62, 0x3E, 0xF7, 0x63, 0xD5, 0x4E, 0x56, 0x5C,\n     0x81, 0x30, 0x53, 0x9D, 0xE5, 0x20, 0xCC, 0xC0, 0x75, 0xF3, 0xE8, 0x79,\n     0xB7, 0xFB, 0x3F, 0xFF, 0xD9\n};\n"
  },
  {
    "path": "src/samples/i18n.h",
    "content": "#pragma once\n\n// String ids must be sequential starting from 1 (not zero!) and\n// be compact sequential integers set (should be compact)\n\n#define str_do_not_use         0\n\n#define str_restore            1\n#define str_maximize           2\n#define str_minimize           3\n#define str_quit               4\n#define str_close              5\n#define str_cancel             6\n#define str_ok                 7\n#define str_full_screen        8\n#define str_yes                9\n#define str_no                10\n#define str_hello             11\n#define str_slider_accel      12\n#define str_file_open         13\n#define str_about             14\n#define str_button_messagebox 15\n#define str_scroll            16\n#define str_reverse           17\n#define str_natural           18\n#define str_mbx               19\n#define str_window            20\n#define str_monitor           21\n#define str_client_area       22\n#define str_mouse             23\n#define str_header            24\n#define str_left_top          25\n#define str_zoom              26\n#define str_mandelbrot        27\n#define str_help              28\n#define str_proportional      29\n#define str_monospaced        30\n#define str_millisecond       31\n#define str_minimum           32\n#define str_average           33\n#define str_maximum           34\n#define str_restore_from_fs   35\n#define str_about_hint        36\n#define str_yes_no_hint       37\n#define str_fs_label          38\n#define str_copied            39\n"
  },
  {
    "path": "src/samples/rt.c",
    "content": "#define rt_implementation\n#include \"single_file_lib/rt/rt.h\"\n"
  },
  {
    "path": "src/samples/sample.rc",
    "content": "﻿#ifdef RC_INVOKED\n#ifndef _INC_WINDOWS\n#define _INC_WINDOWS\n#include \"winres.h\"\n#endif\n#endif\n\n#include \"i18n.h\"\n\n1 RT_MANIFEST \"manifest.xml\"\n101 ICON \"sample.ico\"\nsample_png RCDATA \"sample.png\" // https://en.wikipedia.org/wiki/File:Philips_PM5544.svg\n\n#define company_name \"Insert Company Name Here\"\n#define copyright \"Copyright (C) YYYY Insert Name Here. All rights reserved.\"\n#define file_description \"UI sample app\"\n#define original_file_name \"sample.exe\"\n#define product_name \"sample\"\n\n#include \"..\\inc\\rt\\version.rc.in\"\n\nLANGUAGE LANG_CHINESE, SUBLANG_NEUTRAL\n\n// to localize Chinese variants:\n// use LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL, SUBLANG_CHINESE_HONGKONG\n//                   SUBLANG_CHINESE_SINGAPORE, SUBLANG_CHINESE_MACAU ...\n\nSTRINGTABLE {\n    str_restore           , L\"恢复\"\n    str_maximize          , L\"最大化\"\n    str_minimize          , L\"最小化\"\n    str_quit              , L\"退出\"\n    str_close             , L\"关闭\"\n    str_cancel            , L\"取消\"\n    str_ok                , L\"确定\"\n    str_full_screen       , L\"全屏 (ESC 恢复)\"\n    str_yes               , L\"&Y 是的\"\n    str_no                , L\"&N 不\"\n    str_hello             , L\"你好\"\n    str_slider_accel      , L\" 点击时按住\\n Ctrl：x 10 Shift：x 100 \\n Ctrl+Shift：x 1000 \\n 来调整步进倍数。\"\n    str_file_open         , L\"&O 已连接\"\n    str_about             , L\"&A 盖\"\n    str_button_messagebox , L\"&M 消息框\"\n    str_scroll            , L\"滚动方向：\"\n    str_natural           , L\"自然\"\n    str_reverse           , L\"反向\"\n    str_mbx               , L\"\"\"Pneumonoultramicroscopicsilicovolcanoconiosis\"\"\\n它是最长的英语单词还是不是？\"\n    str_window            , L\"窗口\"\n    str_monitor           , L\"显示器\"\n    str_client_area       , L\"客户区\"\n    str_mouse             , L\"鼠标坐标\"\n    str_header            , L\"标题\"\n    str_left_top          , L\"左上\"\n    str_zoom              , L\"飞涨: 1 / (2^%d)\"\n    str_mandelbrot        , L\"曼德勃罗特探险家\"\n    str_help              , L\"单击内部或+/-放大；\\n右键单击缩小；\\n使用触摸板或键盘←↑↓→移动\"\n    str_proportional      , L\"成比例的\"\n    str_monospaced        , L\"等宽\"\n    str_millisecond       , L\"毫秒\"\n    str_minimum           , L\"最低的\"\n    str_average           , L\"平均\"\n    str_maximum           , L\"最大\"\n    str_restore_from_fs   , L\"从全屏恢复\"\n    str_about_hint        , L\"显示关于消息框\"\n    str_yes_no_hint       , L\"显示是/否消息框\"\n    str_fs_label          , L\"全屏\"\n//  str_copied            , L\"copied to clipboard\"\n    str_copied            , L\"复制到剪贴板\"\n}\n\n// Order of languages is important. (ENGLISH, NEUTRAL) must be last.\n// It is assumed that application messaging is expressed in Neutral\n// English and strings used in the body of code match character by\n// character with the strings in the NEUTRAL ENGLISH STRINGTABLE.\n// It is also assumed that same str_ids are used for all string tables\n// are the same for each unique string in the code.\n\nLANGUAGE LANG_AFRIKAANS, SUBLANG_NEUTRAL // locale: \"af-ZA\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_ALBANIAN, SUBLANG_NEUTRAL // locale: \"sq-AL\"\nSTRINGTABLE {\n    str_hello             , L\"Tungjatjeta\"\n}\n\nLANGUAGE LANG_ALSATIAN, SUBLANG_NEUTRAL // locale: \"gsw-FR\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_AMHARIC, SUBLANG_NEUTRAL // locale: \"am-ET\"\nSTRINGTABLE {\n    str_hello             , L\"ሰላም\"\n}\n\nLANGUAGE LANG_ARABIC, SUBLANG_NEUTRAL // locale: \"ar-SA\"\nSTRINGTABLE {\n    str_hello             , L\"مرحبًا\"\n}\n\nLANGUAGE LANG_ARMENIAN, SUBLANG_NEUTRAL // locale: \"hy-AM\"\nSTRINGTABLE {\n    str_hello             , L\"Բարեւ ծով\"\n}\n\nLANGUAGE LANG_ASSAMESE, SUBLANG_NEUTRAL // locale: \"as-IN\"\nSTRINGTABLE {\n    str_hello             , L\"নমস্কাৰ\"\n}\n\nLANGUAGE LANG_AZERI, SUBLANG_NEUTRAL // locale: \"az-AZ\"\nSTRINGTABLE {\n    str_hello             , L\"Salam\"\n}\n\nLANGUAGE LANG_BANGLA, SUBLANG_NEUTRAL // locale: \"bn-BD\"\nSTRINGTABLE {\n    str_hello             , L\"হ্যালো\"\n}\n\nLANGUAGE LANG_BASHKIR, SUBLANG_NEUTRAL // locale: \"ba-RU\"\nSTRINGTABLE {\n    str_hello             , L\"Хәйерсез\"\n}\n\nLANGUAGE LANG_BASQUE, SUBLANG_NEUTRAL // locale: \"eu-ES\"\nSTRINGTABLE {\n    str_hello             , L\"Kaixo\"\n}\n\nLANGUAGE LANG_BELARUSIAN, SUBLANG_NEUTRAL // locale: \"be-BY\"\nSTRINGTABLE {\n    str_hello             , L\"Прывітанне\"\n}\n\nLANGUAGE LANG_BRETON, SUBLANG_NEUTRAL // locale: \"br-FR\"\nSTRINGTABLE {\n    str_hello             , L\"Salud\"\n}\n\nLANGUAGE LANG_BOSNIAN, SUBLANG_NEUTRAL // locale: \"bs-BA\"\nSTRINGTABLE {\n    str_hello             , L\"Zdravo\"\n}\n\nLANGUAGE LANG_BOSNIAN_NEUTRAL, SUBLANG_NEUTRAL // locale: \"bs-Latn-BA\"\nSTRINGTABLE {\n    str_hello             , L\"Zdravo\"\n}\n\nLANGUAGE LANG_BULGARIAN, SUBLANG_NEUTRAL // locale: \"bg-BG\"\nSTRINGTABLE {\n    str_hello             , L\"Здравейте\"\n}\n\nLANGUAGE LANG_CATALAN, SUBLANG_NEUTRAL // locale: \"ca-ES\"\nSTRINGTABLE {\n    str_hello             , L\"Hola\"\n}\n\nLANGUAGE LANG_CENTRAL_KURDISH, SUBLANG_NEUTRAL // locale: \"ku-CK\"\nSTRINGTABLE {\n    str_hello             , L\"هەڵۆ\"\n}\n\nLANGUAGE LANG_CZECH, SUBLANG_NEUTRAL // locale: \"cs-CZ\"\nSTRINGTABLE {\n    str_hello             , L\"Ahoj\"\n}\n\nLANGUAGE LANG_DANISH, SUBLANG_NEUTRAL // locale: \"da-DK\"\nSTRINGTABLE {\n    str_hello             , L\"Hej\"\n}\n\nLANGUAGE LANG_DARI, SUBLANG_NEUTRAL // locale: \"prs-AF\"\nSTRINGTABLE {\n    str_hello             , L\"سلام\"\n}\n\nLANGUAGE LANG_DIVEHI, SUBLANG_NEUTRAL // locale: \"dv-MV\"\nSTRINGTABLE {\n    str_hello             , L\"ଆଲା\"\n}\n\nLANGUAGE LANG_DUTCH, SUBLANG_NEUTRAL // locale: \"nl-NL\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_ESTONIAN, SUBLANG_NEUTRAL // locale: \"et-EE\"\nSTRINGTABLE {\n    str_hello             , L\"Tere\"\n}\n\nLANGUAGE LANG_FAEROESE, SUBLANG_NEUTRAL // locale: \"fo-FO\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_FARSI, SUBLANG_NEUTRAL // locale: \"fa-IR\"\nSTRINGTABLE {\n    str_hello             , L\"سلام\"\n}\n\nLANGUAGE LANG_FILIPINO, SUBLANG_NEUTRAL // locale: \"fil-PH\"\nSTRINGTABLE {\n    str_hello             , L\"Hello\"\n}\n\nLANGUAGE LANG_FINNISH, SUBLANG_NEUTRAL // locale: \"fi-FI\"\nSTRINGTABLE {\n    str_hello             , L\"Hei\"\n}\n\nLANGUAGE LANG_FRENCH, SUBLANG_NEUTRAL // locale: \"fr-FR\"\nSTRINGTABLE {\n    str_hello             , L\"Bonjour\"\n}\n\nLANGUAGE LANG_FRISIAN, SUBLANG_NEUTRAL // locale: \"fy-NL\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_GALICIAN, SUBLANG_NEUTRAL // locale: \"gl-ES\"\nSTRINGTABLE {\n    str_hello             , L\"Ola\"\n}\n\nLANGUAGE LANG_GEORGIAN, SUBLANG_NEUTRAL // locale: \"ka-GE\"\nSTRINGTABLE {\n    str_hello             , L\"გამარჯობა\"\n}\n\nLANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL // locale: \"de-DE\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_GREEK, SUBLANG_NEUTRAL // locale: \"el-GR\"\nSTRINGTABLE {\n    str_hello             , L\"Γεια σας\"\n}\n\nLANGUAGE LANG_GREENLANDIC, SUBLANG_NEUTRAL // locale: \"kl-GL\"\nSTRINGTABLE {\n    str_hello             , L\"Aliorneq\"\n}\n\nLANGUAGE LANG_GUJARATI, SUBLANG_NEUTRAL // locale: \"gu-IN\"\nSTRINGTABLE {\n    str_hello             , L\"કેમ છો\"\n}\n\nLANGUAGE LANG_HAUSA, SUBLANG_NEUTRAL // locale: \"ha-Latn-NG\"\nSTRINGTABLE {\n    str_hello             , L\"Sannu\"\n}\n\nLANGUAGE LANG_HAWAIIAN, SUBLANG_NEUTRAL // locale: \"haw-US\"\nSTRINGTABLE {\n    str_hello             , L\"Aloha\"\n}\n\nLANGUAGE LANG_HEBREW, SUBLANG_NEUTRAL // locale: \"he-IL\"\nSTRINGTABLE {\n    str_hello             , L\"שלום\"\n}\n\nLANGUAGE LANG_HINDI, SUBLANG_NEUTRAL // locale: \"hi-IN\"\nSTRINGTABLE {\n    str_hello             , L\"नमस्ते\"\n}\n\nLANGUAGE LANG_HUNGARIAN, SUBLANG_NEUTRAL // locale: \"hu-HU\"\nSTRINGTABLE {\n    str_hello             , L\"Helló\"\n}\n\nLANGUAGE LANG_ICELANDIC, SUBLANG_NEUTRAL // locale: \"is-IS\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_IGBO, SUBLANG_NEUTRAL // locale: \"ig-NG\"\nSTRINGTABLE {\n    str_hello             , L\"Ndeewọ\"\n}\n\nLANGUAGE LANG_INDONESIAN, SUBLANG_NEUTRAL // locale: \"id-ID\"\nSTRINGTABLE {\n    str_hello             , L\"Halo\"\n}\n\nLANGUAGE LANG_INUKTITUT, SUBLANG_NEUTRAL // locale: \"iu-Latn-CA\"\nSTRINGTABLE {\n    str_hello             , L\"Hi\"\n}\n\nLANGUAGE LANG_IRISH, SUBLANG_NEUTRAL // locale: \"ga-IE\"\nSTRINGTABLE {\n    str_hello             , L\"Dia dhuit\"\n}\n\nLANGUAGE LANG_ITALIAN, SUBLANG_NEUTRAL // locale: \"it-IT\"\nSTRINGTABLE {\n    str_hello             , L\"Ciao\"\n}\n\nLANGUAGE LANG_JAPANESE, SUBLANG_NEUTRAL // locale: \"ja-JP\"\nSTRINGTABLE {\n    str_hello             , L\"こんにちは\"\n}\n\nLANGUAGE LANG_KANNADA, SUBLANG_NEUTRAL // locale: \"kn-IN\"\nSTRINGTABLE {\n    str_hello             , L\"ಹಲೋ\"\n}\n\nLANGUAGE LANG_KAZAK, SUBLANG_NEUTRAL // locale: \"kk-KZ\"\nSTRINGTABLE {\n    str_hello             , L\"Сәлем\"\n}\n\nLANGUAGE LANG_KHMER, SUBLANG_NEUTRAL // locale: \"km-KH\"\nSTRINGTABLE {\n    str_hello             , L\"សួស្ត\"\n}\n\nLANGUAGE LANG_KINYARWANDA, SUBLANG_NEUTRAL // locale: \"rw-RW\"\nSTRINGTABLE {\n    str_hello             , L\"Murakaza\"\n}\n\nLANGUAGE LANG_KONKANI, SUBLANG_NEUTRAL // locale: \"kok-IN\"\nSTRINGTABLE {\n    str_hello             , L\"Khallô\"\n}\n\nLANGUAGE LANG_KOREAN, SUBLANG_NEUTRAL // locale: \"ko-KR\"\nSTRINGTABLE {\n    str_hello             , L\"안녕하세요\"\n}\n\nLANGUAGE LANG_KYRGYZ, SUBLANG_NEUTRAL // locale: \"ky-KG\"\nSTRINGTABLE {\n    str_hello             , L\"Салам\"\n}\n\nLANGUAGE LANG_LAO, SUBLANG_NEUTRAL // locale: \"lo-LA\"\nSTRINGTABLE {\n    str_hello             , L\"ສະບາຍດີ\"\n}\n\nLANGUAGE LANG_LATVIAN, SUBLANG_NEUTRAL // locale: \"lv-LV\"\nSTRINGTABLE {\n    str_hello             , L\"Sveiki\"\n}\n\nLANGUAGE LANG_LITHUANIAN, SUBLANG_NEUTRAL // locale: \"lt-LT\"\nSTRINGTABLE {\n    str_hello             , L\"Labas\"\n}\n\nLANGUAGE LANG_LUXEMBOURGISH, SUBLANG_NEUTRAL // locale: \"lb-LU\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_MACEDONIAN, SUBLANG_NEUTRAL // locale: \"mk-MK\"\nSTRINGTABLE {\n    str_hello             , L\"Здраво\"\n}\n\nLANGUAGE LANG_MALAY, SUBLANG_NEUTRAL // locale: \"ms-MY\"\nSTRINGTABLE {\n    str_hello             , L\"Hello\"\n}\n\nLANGUAGE LANG_MALAYALAM, SUBLANG_NEUTRAL // locale: \"ml-IN\"\nSTRINGTABLE {\n    str_hello             , L\"ഹായ്\"\n}\n\nLANGUAGE LANG_MALTESE, SUBLANG_NEUTRAL // locale: \"mt-MT\"\nSTRINGTABLE {\n    str_hello             , L\"Hello\"\n}\n\nLANGUAGE LANG_MANIPURI, SUBLANG_NEUTRAL // locale: \"mni-IN\"\nSTRINGTABLE {\n    str_hello             , L\"আনৰ এইদিনৰ \"\n}\n\nLANGUAGE LANG_MAORI, SUBLANG_NEUTRAL // locale: \"mi-NZ\"\nSTRINGTABLE {\n    str_hello             , L\"Kia ora\"\n}\n\nLANGUAGE LANG_MARATHI, SUBLANG_NEUTRAL // locale: \"mr-IN\"\nSTRINGTABLE {\n    str_hello             , L\"नमस्कार\"\n}\n\nLANGUAGE LANG_MOHAWK, SUBLANG_NEUTRAL // locale: \"moh-CA\"\nSTRINGTABLE {\n    str_hello             , L\"E:nen\"\n}\n\nLANGUAGE LANG_MONGOLIAN, SUBLANG_NEUTRAL // locale: \"mn-MN\"\nSTRINGTABLE {\n    str_hello             , L\"Сайн уу\"\n}\n\nLANGUAGE LANG_NEPALI, SUBLANG_NEUTRAL // locale: \"ne-NP\"\nSTRINGTABLE {\n    str_hello             , L\"नमस्ते\"\n}\n\nLANGUAGE LANG_NORWEGIAN, SUBLANG_NEUTRAL // locale: \"nb-NO\"\nSTRINGTABLE {\n    str_hello             , L\"Hallo\"\n}\n\nLANGUAGE LANG_OCCITAN, SUBLANG_NEUTRAL // locale: \"oc-FR\"\nSTRINGTABLE {\n    str_hello             , L\"Alau\"\n}\n\nLANGUAGE LANG_ODIA, SUBLANG_NEUTRAL // locale: \"or-IN\"\nSTRINGTABLE {\n    str_hello             , L\"ନମସ୍କାର\"\n}\n\nLANGUAGE LANG_POLISH, SUBLANG_NEUTRAL // locale: \"pl-PL\"\nSTRINGTABLE {\n    str_hello             , L\"Cześć\"\n}\n\nLANGUAGE LANG_PORTUGUESE, SUBLANG_NEUTRAL // locale: \"pt-PT\"\nSTRINGTABLE {\n    str_hello             , L\"Olá\"\n}\n\nLANGUAGE LANG_PUNJABI, SUBLANG_NEUTRAL // locale: \"pa-IN\"\nSTRINGTABLE {\n    str_hello             , L\"ਸਤਿ ਸ੍ਰੀ ਅਕਾਲ\"\n}\n\nLANGUAGE LANG_ROMANIAN, SUBLANG_NEUTRAL // locale: \"ro-RO\"\nSTRINGTABLE {\n    str_hello             , L\"Bună ziua\"\n}\n\nLANGUAGE LANG_ROMANSH, SUBLANG_NEUTRAL // locale: \"rm-CH\"\nSTRINGTABLE {\n    str_hello             , L\"Bun di\"\n}\n\nLANGUAGE LANG_RUSSIAN, SUBLANG_NEUTRAL // locale: \"ru-RU\"\nSTRINGTABLE {\n    str_hello             , L\"Привет\"\n}\n\nLANGUAGE LANG_SAKHA, SUBLANG_NEUTRAL // locale: \"sah-RU\"\nSTRINGTABLE {\n    str_hello             , L\"Саламыт\"\n}\n\nLANGUAGE LANG_SAMI, SUBLANG_NEUTRAL // locale: \"se-NO\"\nSTRINGTABLE {\n    str_hello             , L\"Bures\"\n}\n\nLANGUAGE LANG_SANSKRIT, SUBLANG_NEUTRAL // locale: \"sa-IN\"\nSTRINGTABLE {\n    str_hello             , L\"नमस्ते\"\n}\n\nLANGUAGE LANG_SCOTTISH_GAELIC, SUBLANG_NEUTRAL // locale: \"gd-GB\"\nSTRINGTABLE {\n    str_hello             , L\"Halò\"\n}\n\nLANGUAGE LANG_SERBIAN_NEUTRAL, SUBLANG_NEUTRAL // locale: \"sr-Latn-RS\"\nSTRINGTABLE {\n    str_hello             , L\"Zdravo\"\n}\n\nLANGUAGE LANG_SINHALESE, SUBLANG_NEUTRAL // locale: \"si-LK\"\nSTRINGTABLE {\n    str_hello             , L\"හෙලෝ\"\n}\n\nLANGUAGE LANG_SLOVAK, SUBLANG_NEUTRAL // locale: \"sk-SK\"\nSTRINGTABLE {\n    str_hello             , L\"Ahoj\"\n}\n\nLANGUAGE LANG_SLOVENIAN, SUBLANG_NEUTRAL // locale: \"sl-SI\"\nSTRINGTABLE {\n    str_hello             , L\"Zdravo\"\n}\n\nLANGUAGE LANG_SOTHO, SUBLANG_NEUTRAL // locale: \"st-ZA\"\nSTRINGTABLE {\n    str_hello             , L\"Dumela\"\n}\n\nLANGUAGE LANG_SPANISH, SUBLANG_NEUTRAL // locale: \"es-ES\"\nSTRINGTABLE {\n    str_hello             , L\"Hola\"\n}\n\nLANGUAGE LANG_SWAHILI, SUBLANG_NEUTRAL // locale: \"sw-KE\"\nSTRINGTABLE {\n    str_hello             , L\"Jambo\"\n}\n\nLANGUAGE LANG_SWEDISH, SUBLANG_NEUTRAL // locale: \"sv-SE\"\nSTRINGTABLE {\n    str_hello             , L\"Hej\"\n}\n\nLANGUAGE LANG_TAJIK, SUBLANG_NEUTRAL // locale: \"tg-TJ\"\nSTRINGTABLE {\n    str_hello             , L\"Салом\"\n}\n\nLANGUAGE LANG_TAMIL, SUBLANG_NEUTRAL // locale: \"ta-IN\"\nSTRINGTABLE {\n    str_hello             , L\"வணக்கம்\"\n}\n\nLANGUAGE LANG_TATAR, SUBLANG_NEUTRAL // locale: \"tt-RU\"\nSTRINGTABLE {\n    str_hello             , L\"Сәлам\"\n}\n\nLANGUAGE LANG_TELUGU, SUBLANG_NEUTRAL // locale: \"te-IN\"\nSTRINGTABLE {\n    str_hello             , L\"హలో\"\n}\n\nLANGUAGE LANG_THAI, SUBLANG_NEUTRAL // locale: \"th-TH\"\nSTRINGTABLE {\n    str_hello             , L\"สวัสดี\"\n}\n\nLANGUAGE LANG_TIBETAN, SUBLANG_NEUTRAL // locale: \"bo-CN\"\nSTRINGTABLE {\n    str_hello             , L\"དེ་ལེགས་སོགས་པ།\"\n}\n\nLANGUAGE LANG_TIGRIGNA, SUBLANG_NEUTRAL // locale: \"ti-ER\"\nSTRINGTABLE {\n    str_hello             , L\"ሰላም\"\n}\n\nLANGUAGE LANG_TSWANA, SUBLANG_NEUTRAL // locale: \"tn-ZA\"\nSTRINGTABLE {\n    str_hello             , L\"Dumela\"\n}\n\nLANGUAGE LANG_TURKISH, SUBLANG_NEUTRAL // locale: \"tr-TR\"\nSTRINGTABLE {\n    str_hello             , L\"Merhaba\"\n}\n\nLANGUAGE LANG_TURKMEN, SUBLANG_NEUTRAL // locale: \"tk-TM\"\nSTRINGTABLE {\n    str_hello             , L\"Günaydın\"\n}\n\nLANGUAGE LANG_UIGHUR, SUBLANG_NEUTRAL // locale: \"ug-CN\"\nSTRINGTABLE {\n    str_hello             , L\"سالام\"\n}\n\nLANGUAGE LANG_UKRAINIAN, SUBLANG_NEUTRAL // locale: \"uk-UA\"\nSTRINGTABLE {\n    str_hello             , L\"Привіт\"\n}\n\nLANGUAGE LANG_URDU, SUBLANG_NEUTRAL // locale: \"ur-PK\"\nSTRINGTABLE {\n    str_hello             , L\"ہیلو\"\n}\n\nLANGUAGE LANG_UZBEK, SUBLANG_NEUTRAL // locale: \"uz-UZ\"\nSTRINGTABLE {\n    str_hello             , L\"Salom\"\n}\n\nLANGUAGE LANG_VIETNAMESE, SUBLANG_NEUTRAL // locale: \"vi-VN\"\nSTRINGTABLE {\n    str_hello             , L\"Xin chào\"\n}\n\nLANGUAGE LANG_WELSH, SUBLANG_NEUTRAL // locale: \"cy-GB\"\nSTRINGTABLE {\n    str_hello             , L\"Helo\"\n}\n\nLANGUAGE LANG_WOLOF, SUBLANG_NEUTRAL // locale: \"wo-SN\"\nSTRINGTABLE {\n    str_hello             , L\"Ndamli\"\n}\n\nLANGUAGE LANG_XHOSA, SUBLANG_NEUTRAL // locale: \"xh-ZA\"\nSTRINGTABLE {\n    str_hello             , L\"Molo\"\n}\n\nLANGUAGE LANG_YORUBA, SUBLANG_NEUTRAL // locale: \"yo-NG\"\nSTRINGTABLE {\n    str_hello             , L\"Mo ke fun o\"\n}\n\nLANGUAGE LANG_ZULU, SUBLANG_NEUTRAL // locale: \"zu-ZA\"\nSTRINGTABLE {\n    str_hello             , L\"Sawubona\"\n}\n\nLANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL\n\n// to localize English variants:\n// use LANG_ENGLISH, SUBLANG_ENGLISH_US, SUBLANG_ENGLISH_UK ...\n\nSTRINGTABLE {\n    str_restore           , L\"Restore\"\n    str_maximize          , L\"Maximize\"\n    str_minimize          , L\"Minimize\"\n    str_quit              , L\"Quit\"\n    str_close             , L\"Close\"\n    str_cancel            , L\"Cancel\"\n    str_ok                , L\"OK\"\n    str_full_screen       , L\"Full Screen (ESC to restore)\"\n    str_yes               , L\"&Yes\"\n    str_no                , L\"&No\"\n    str_hello             , L\"Hello\"\n    str_slider_accel      , L\" Hold key while clicking\\n Ctrl: x 10 Shift: x 100 \\n Ctrl+Shift: x 1000 \\n for step multiplier.\"\n    str_file_open         , L\"&Open\"\n    str_about             , L\"&About\"\n    str_button_messagebox , L\"&Message Box\"\n    str_scroll            , L\"Scroll &Direction:\"\n    str_natural           , L\"Natural\"\n    str_reverse           , L\"Reverse\"\n    str_mbx               , L\"\"\"Pneumonoultramicroscopicsilicovolcanoconiosis\"\"\\nis it the longest English language word or not?\"\n    str_window            , L\"Window\"\n    str_monitor           , L\"Monitor\"\n    str_client_area       , L\"Client area\"\n    str_mouse             , L\"Mouse\"\n    str_header            , L\"Header\"\n    str_left_top          , L\"Left Top\"\n    str_zoom              , L\"Zoom: 1 / (2^%d)\"\n    str_mandelbrot        , L\"Mandelbrot Explorer\"\n    str_help              , L\"Click inside or +/- to zoom;\\nright mouse click to zoom out;\\nuse touchpad or keyboard ←↑↓→ to pan\"\n    str_proportional      , L\"Proportional\"\n    str_monospaced        , L\"Monospaced\"\n    str_millisecond       , L\"ms\"\n    str_minimum           , L\"min\"\n    str_average           , L\"avg\"\n    str_maximum           , L\"max\"\n    str_restore_from_fs   , L\"Restore from &Full Screen\"\n    str_about_hint        , L\"Show About message box\"\n    str_yes_no_hint       , L\"Show Yes/No message box\"\n    str_fs_label          , L\"&Full Screen\"\n    str_copied            , L\"copied to clipboard\"\n}\n"
  },
  {
    "path": "src/samples/sample1.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic char title[128] = \"Polyglot\"; // https://youtu.be/D36zd8yNTbQ\n\nstatic const char* locales[] = { // 123 languages\n    \"af-ZA\", \"am-ET\", \"ar-SA\", \"as-IN\", \"az-AZ\", \"ba-RU\", \"be-BY\", \"bg-BG\",\n    \"bn-BD\", \"bo-CN\", \"br-FR\", \"bs-BA\", \"bs-Latn-BA\", \"ca-ES\", \"ca-ES-Valencia\",\n    \"cs-CZ\", \"cy-GB\", \"da-DK\", \"de-DE\", \"dv-MV\", \"el-GR\", \"en-US\",\n    \"es-ES\", \"et-EE\", \"eu-ES\", \"fa-IR\", \"ff-Latn-SN\", \"fi-FI\", \"fil-PH\",\n    \"fo-FO\", \"fr-FR\", \"fy-NL\", \"ga-IE\", \"gd-GB\", \"gl-ES\", \"gsw-FR\", \"gu-IN\",\n    \"ha-Latn-NG\", \"haw-US\", \"he-IL\", \"hi-IN\", \"hr-HR\", \"hsb-DE\", \"hu-HU\",\n    \"hy-AM\", \"ig-NG\", \"id-ID\", \"is-IS\", \"it-IT\", \"iu-Latn-CA\", \"ja-JP\", \"kk-KZ\",\n    \"kl-GL\", \"km-KH\", \"kn-IN\", \"kok-IN\", \"ko-KR\", \"ku-CK\", \"ky-KG\", \"lb-LU\",\n    \"lo-LA\", \"lt-LT\", \"lv-LV\", \"mni-IN\", \"mk-MK\", \"ml-IN\", \"mn-MN\", \"moh-CA\",\n    \"mr-IN\", \"ms-MY\", \"mt-MT\", \"mi-NZ\", \"ne-NP\", \"nb-NO\", \"nl-NL\", \"oc-FR\",\n    \"or-IN\", \"pa-IN\", \"pl-PL\", \"prs-AF\", \"ps-AF\", \"pt-PT\", \"ro-RO\", \"rm-CH\",\n    \"ru-RU\", \"rw-RW\", \"sa-IN\", \"sah-RU\", \"se-NO\", \"si-LK\", \"sk-SK\", \"sl-SI\",\n    \"sr-RS\", \"sr-Latn-RS\", \"st-ZA\", \"sv-SE\", \"sw-KE\", \"ta-IN\", \"te-IN\", \"tg-TJ\",\n    \"th-TH\", \"ti-ER\", \"tk-TM\", \"tn-ZA\", \"tr-TR\", \"tt-RU\", \"ug-CN\", \"uk-UA\",\n    \"ur-PK\", \"uz-UZ\", \"vi-VN\", \"wo-SN\", \"xh-ZA\", \"yo-NG\", \"zu-ZA\"\n};\n\nstatic int32_t locale;\nstatic ui_label_t label = ui_label(0.0, \"Hello\");\n\nstatic void every_sec(ui_view_t* rt_unused(v)) {\n    rt_nls.set_locale(locales[locale]);\n    rt_str_printf(title, \"Polyglot [%s]\", locales[locale]);\n    ui_app.set_title(title);\n    ui_app.request_layout();\n    locale = (locale + 1) % rt_countof(locales);\n}\n\nstatic bool tap(ui_view_t* v, int32_t ix, bool pressed) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    rt_println(\"ix: %d inside: %d %s\", ix, inside, pressed ? \"dw\" : \"up\");\n    return inside;\n}\n\nstatic bool long_press(ui_view_t* v, int32_t ix) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    rt_println(\"ix: %d inside: %d\", ix, inside);\n    return inside;\n}\n\nstatic bool double_tap(ui_view_t* v, int32_t ix) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    rt_println(\"ix: %d inside: %d\", ix, inside);\n    return inside;\n}\n\nstatic void opened(void) {\n    static ui_fm_t fm;\n    ui_gdi.update_fm(&fm, ui_gdi.create_font(\"Segoe Script\", ui_app.in2px(0.5), -1));\n    ui_app.content->every_sec = every_sec;\n    label.fm = &fm;\n    ui_view.add(ui_app.content, &label, null);\n    locale = (int32_t)(rt_clock.nanoseconds() & 0xFFFF % rt_countof(locales));\n    label.tap = tap;\n    label.long_press = long_press;\n    label.double_tap = double_tap;\n}\nstatic void init(void) {\n    ui_app.title = title;\n    ui_app.opened = opened;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample1\",\n    .dark_mode = true,\n    .init = init,\n    .window_sizing = {\n        .min_w = 4.0f, // 4.0x1.5 inches\n        .min_h = 1.5f,\n        .ini_w = 4.0f, // 4x2 inches\n        .ini_h = 2.0f\n    }\n};\n"
  },
  {
    "path": "src/samples/sample2.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n\nstatic int64_t hit_test(const ui_view_t* v, ui_point_t pt) {\n    rt_swear(v == ui_app.content);\n    if (ui_view.inside(v, &pt)) {\n        if (pt.y < v->fm->em.h && ui_app.caption->state.hidden) {\n            ui_app.caption->state.hidden = false;\n            ui_app.request_layout();\n        } else if (pt.y > v->fm->em.h && !ui_app.caption->state.hidden) {\n            ui_app.caption->state.hidden = true;\n            ui_app.request_layout();\n        }\n        return ui.hit_test.caption;\n    }\n    return ui.hit_test.nowhere;\n}\n\nstatic void opened(void) {\n//  ui_app.content->insets = (ui_margins_t){ 0, 0, 0, 0 };\n    static ui_label_t hello = ui_label(0.0, \"Hello\");\n//  hello.padding = (ui_margins_t){ 0, 0, 0, 0 };\n//  hello.insets  = (ui_margins_t){ 0, 0, 0, 0 };\n    static ui_fm_t fm;\n    ui_gdi.update_fm(&fm, ui_gdi.create_font(\"Segoe Script\", ui_app.in2px(0.5f), -1));\n    hello.fm = &fm;\n    ui_app.set_layered_window(ui_color_rgb(30, 30, 30), 0.75f);\n    ui_view.add_last(ui_app.content, &hello);\n    ui_app.caption->state.hidden = true;\n    ui_app.content->hit_test = hit_test;\n}\n\nstatic void character(ui_view_t* rt_unused(v), const char* utf8) {\n    if (utf8[0] == 033) { // escape\n        if (!ui_app.is_full_screen) { ui_app.quit(0); }\n        if ( ui_app.is_full_screen) { ui_app.full_screen(false); }\n    }\n}\n\nstatic bool key_pressed(ui_view_t* rt_unused(v), int64_t key) {\n    bool swallow = key == ui.key.f11;\n    if (swallow) { ui_app.full_screen(!ui_app.is_full_screen); }\n    return swallow;\n}\n\nstatic void init(void) {\n    ui_app.title  = \"Sample2: translucent\";\n    ui_app.opened = opened;\n    ui_app.root->character = character;\n    ui_app.root->key_pressed = key_pressed;\n    // for custom caption or no caption .no_decor can be set to true\n    ui_app.no_decor = true;\n    ui_caption.menu.state.hidden = true;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample2\",\n    .dark_mode = true,\n    .init = init,\n    .window_sizing = {\n        .min_w =  1.8f, // 2x1 inches\n        .min_h =  1.0f,\n        .ini_w =  4.0f, // 4x2 inches\n        .ini_h =  2.0f\n    }\n};\n"
  },
  {
    "path": "src/samples/sample3.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n\nstatic volatile int32_t index; // index of image to paint, !ix to render\nstatic ui_bitmap_t image[2];\nstatic uint8_t pixels[2][4 * 4096 * 4096];\n\nstatic rt_thread_t thread;\nstatic rt_event_t wake;\nstatic rt_event_t quit;\n\nstatic volatile bool rendering;\nstatic volatile bool stop;\nstatic volatile fp64_t render_time;\n\nstatic void toggle_full_screen(ui_button_t* b) {\n    b->state.pressed = !b->state.pressed;\n    ui_app.full_screen(b->state.pressed);\n    ui_view.set_text(b, \"%s\", !b->state.pressed ?\n        rt_glyph_square_four_corners : rt_glyph_two_joined_squares);\n}\n\nui_button_clicked(button_fs, rt_glyph_square_four_corners, 1.0, {\n    toggle_full_screen(button_fs);\n});\n\nstatic void paint(ui_view_t* view) {\n    int32_t k = index;\n    ui_gdi.bitmap(0, 0, view->w, view->h,\n                 0, 0, image[k].w, image[k].h, &image[k]);\n    int32_t tx = view->fm->em.w;\n    int32_t ty = view->fm->em.h / 4;\n    const ui_gdi_ta_t ta = { .fm = view->fm, .color = ui_colors.orange };\n    ui_gdi.text(&ta, tx, ty, \"%s\",\n                     \"Try Full Screen Button there --->\");\n    ty = view->h - view->fm->em.h * 3 / 2;\n    ui_gdi.text(&ta, tx, ty,\n        \"render time %.1f ms / avg paint time %.1f ms\",\n        render_time * 1000, ui_app.paint_avg * 1000);\n    if (!rendering) {\n        ui_app.set_cursor(ui_app.cursors.arrow);\n    }\n}\n\nstatic void request_rendering(void) {\n    ui_app.set_cursor(ui_app.cursors.wait);\n    rendering = true;\n    rt_event.set(wake);\n}\n\nstatic void stop_rendering(void) {\n    if (rendering) {\n        stop = true;\n        while (rendering || stop) { rt_thread.sleep_for(0.01); }\n        ui_app.set_cursor(ui_app.cursors.arrow);\n    }\n}\n\nstatic void measure(ui_view_t* view) {\n    view->w = ui_app.root->w;\n    view->h = ui_app.root->h;\n    const int32_t w = view->w;\n    const int32_t h = view->h;\n    ui_bitmap_t* im = &image[index];\n    if (w != im->w || h != im->h) {\n        stop_rendering();\n        im = &image[!index];\n        ui_gdi.bitmap_dispose(im);\n        rt_fatal_if(w * h * 4 > rt_countof(pixels[!index]),\n            \"increase size of pixels[][%d * %d * 4]\", w, h);\n        ui_gdi.bitmap_init(im, w, h, 4, pixels[!index]);\n        request_rendering();\n    }\n}\n\nstatic void layout(ui_view_t* v) {\n    button_fs.x = v->w - button_fs.w - v->fm->em.w / 4;\n    button_fs.y = v->fm->em.h / 4;\n}\n\nstatic void renderer(void* unused); // renderer thread\n\nstatic void character(ui_view_t* rt_unused(view), const char* utf8) {\n    char ch = utf8[0];\n    if (ch == 'q' || ch == 'Q') { ui_app.close(); }\n    if (ui_app.is_full_screen && ch == 033) {\n        toggle_full_screen(&button_fs);\n    }\n}\n\nstatic void closed(void) {\n    rt_event.set(quit);\n    rt_thread.join(thread, -1);\n    thread = null;\n    ui_gdi.bitmap_dispose(&image[0]);\n    ui_gdi.bitmap_dispose(&image[1]);\n}\n\nstatic void fini(void) {\n    rt_event.dispose(wake);\n    rt_event.dispose(quit);\n    wake = null;\n    quit = null;\n}\n\nstatic void opened(void) {\n    rt_fatal_if(ui_app.root->w * ui_app.root->h * 4 > rt_countof(pixels[0]),\n        \"increase size of pixels[][%d * %d * 4]\", ui_app.root->w, ui_app.root->h);\n    ui_app.fini = fini;\n    ui_app.closed = closed;\n    ui_view.add(ui_app.content, &button_fs, null);\n    ui_app.content->layout    = layout;\n    ui_app.content->measure   = measure;\n    ui_app.content->paint     = paint;\n    ui_app.content->character = character;\n    wake = rt_event.create();\n    quit = rt_event.create();\n    // images:\n    ui_gdi.bitmap_init(&image[0], ui_app.root->w, ui_app.root->h, 4, pixels[0]);\n    ui_gdi.bitmap_init(&image[1], ui_app.root->w, ui_app.root->h, 4, pixels[1]);\n    thread = rt_thread.start(renderer, null);\n    request_rendering();\n    rt_str_printf(button_fs.hint, \"&Full Screen\");\n    button_fs.shortcut = 'F';\n}\n\nstatic void init(void) {\n    ui_app.opened = opened;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample3\",\n    .title = \"Sample3: Mandelbrot\",\n    .dark_mode = true,\n    .init = init,\n    // 6x4 inches. Thinking of 6x4 timbers columns, beams, supporting posts :)\n    .window_sizing = {\n        .min_w =  6.0f,\n        .min_h =  4.0f,\n        .ini_w =  6.0f,\n        .ini_h =  4.0f\n    }\n};\n\nstatic fp64_t scale(int32_t x, int32_t n, fp64_t low, fp64_t hi) {\n    return x / (fp64_t)(n - 1) * (hi - low) + low;\n}\n\nstatic void mandelbrot(ui_bitmap_t* im) {\n    fp64_t time = rt_clock.seconds();\n    for (int32_t r = 0; r < im->h && !stop; r++) {\n        fp64_t y0 = scale(r, im->h, -1.12, 1.12);\n        for (int32_t c = 0; c < im->w && !stop; c++) {\n            fp64_t x0 = scale(c, im->w, -2.00, 0.47);\n            fp64_t x = 0;\n            fp64_t y = 0;\n            int32_t iteration = 0;\n            enum { max_iteration = 100 };\n            while (x* x + y * y <= 2 * 2 && iteration < max_iteration && !stop) {\n                fp64_t t = x * x - y * y + x0;\n                y = 2 * x * y + y0;\n                x = t;\n                iteration++;\n            }\n            static ui_color_t palette[16] = {\n                ui_color_rgb( 66,  30,  15),  ui_color_rgb( 25,   7,  26),\n                ui_color_rgb(  9,   1,  47),  ui_color_rgb(  4,   4,  73),\n                ui_color_rgb(  0,   7, 100),  ui_color_rgb( 12,  44, 138),\n                ui_color_rgb( 24,  82, 177),  ui_color_rgb( 57, 125, 209),\n                ui_color_rgb(134, 181, 229),  ui_color_rgb(211, 236, 248),\n                ui_color_rgb(241, 233, 191),  ui_color_rgb(248, 201,  95),\n                ui_color_rgb(255, 170,   0),  ui_color_rgb(204, 128,   0),\n                ui_color_rgb(153,  87,   0),  ui_color_rgb(106,  52,   3)\n            };\n            ui_color_t color = palette[iteration % rt_countof(palette)];\n            uint8_t* px = &((uint8_t*)im->pixels)[r * im->w * 4 + c * 4];\n            px[3] = 0xFF;\n            px[0] = (color >> 16) & 0xFF;\n            px[1] = (color >>  8) & 0xFF;\n            px[2] = (color >>  0) & 0xFF;\n        }\n    }\n    render_time = rt_clock.seconds() - time;\n}\n\nstatic void renderer(void* unused) {\n    (void)unused;\n    rt_thread.name(\"renderer\");\n    rt_thread.realtime();\n    rt_event_t es[2] = {wake, quit};\n    for (;;) {\n        int32_t ix = rt_event.wait_any(rt_countof(es), es);\n        if (ix != 0) { break; }\n        int32_t k = !index;\n        mandelbrot(&image[k]);\n        if (!stop) { index = !index; ui_app.request_redraw(); }\n        stop = false;\n        rendering = false;\n    }\n}\n"
  },
  {
    "path": "src/samples/sample4.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n#include \"stb_image.h\"\n\nconst char* title = \"Sample4\";\n\nstatic ui_bitmap_t image[2];\n\nstatic char filename[260]; // c:\\Users\\user\\Pictures\\mandrill-4.2.03.png\n\nstatic void init(void);\n\nstatic int  console(void) {\n    rt_fatal_if(true, \"%s only SUBSYSTEM:WINDOWS\", rt_args.basename());\n    return 1;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample4\",\n    .init = init,\n    .dark_mode = true,\n    .main = console,\n    .window_sizing = {\n        .min_w =  4.0f,\n        .min_h =  4.0f,\n        .ini_w =  6.0f,\n        .ini_h =  6.0f\n    }\n};\n\nstatic void* load_image(const uint8_t* data, int64_t bytes, int32_t* w, int32_t* h,\n    int32_t* bpp, int32_t preferred_bytes_per_pixel);\n\nstatic void load_images(void) {\n    int r = 0;\n    void* data = null;\n    int64_t bytes = 0;\n    for (int i = 0; i < rt_countof(image); i++) {\n        if (i == 0) {\n            r = rt_mem.map_ro(filename, &data, &bytes);\n        } else {\n            r = rt_mem.map_resource(\"sample_png\", &data, &bytes);\n        }\n        rt_fatal_if_error(r);\n        int w = 0;\n        int h = 0;\n        int bpp = 0; // bytes (!) per pixel\n        void* pixels = load_image(data, bytes, &w, &h, &bpp, 0);\n        rt_not_null(pixels);\n        ui_gdi.bitmap_init(&image[i], w, h, bpp, pixels);\n        stbi_image_free(pixels);\n        // do not unmap resources:\n        if (i == 0) { rt_mem.unmap(data, bytes); }\n    }\n}\n\nstatic void paint(ui_view_t* view) {\n    ui_gdi.fill(0, 0, view->w, view->h, ui_colors.black);\n    if (image[1].w > 0 && image[1].h > 0) {\n        int w = rt_min(view->w, image[1].w);\n        int h = rt_min(view->h, image[1].h);\n        int x = (view->w - w) / 2;\n        int y = (view->h - h) / 2;\n        ui_gdi.set_clip(0, 0, view->w, view->h);\n        ui_gdi.bitmap(x, y, w, h, 0, 0, image[1].w, image[1].h, &image[1]);\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n    if (image[0].w > 0 && image[0].h > 0) {\n        int x = (view->w - image[0].w) / 2;\n        int y = (view->h - image[0].h) / 2;\n        ui_gdi.bitmap(x, y, image[0].w, image[0].h,\n                     0, 0, image[0].w, image[0].h, &image[0]);\n    }\n}\n\nstatic void download(void) {\n    static const char* url =\n        \"https://upload.wikimedia.org/wikipedia/commons/c/c1/\"\n        \"Wikipedia-sipi-image-db-mandrill-4.2.03.png\";\n    if (!rt_files.exists(filename)) {\n        char cmd[256];\n        rt_str_printf(cmd, \"curl.exe  --silent --fail --create-dirs \"\n            \"\\\"%s\\\" --output \\\"%s\\\" 2>nul >nul\", url, filename);\n        int r = system(cmd);\n        if (r != 0) {\n            rt_println(\"download %s failed %d %s\", filename, r, rt_strerr(r));\n        }\n    }\n}\n\nstatic void init(void) {\n    ui_app.title = title;\n    ui_app.content->paint = paint;\n    rt_str_printf(filename, \"%s\\\\mandrill-4.2.03.png\",\n        rt_files.known_folder(rt_files.folder.pictures));\n    download();\n    load_images();\n}\n\nstatic void* load_image(const uint8_t* data, int64_t bytes, int32_t* w, int32_t* h,\n    int32_t* bpp, int32_t preferred_bytes_per_pixel) {\n    void* pixels = stbi_load_from_memory((uint8_t const*)data, (int32_t)bytes, w, h,\n        bpp, preferred_bytes_per_pixel);\n    return pixels;\n}\n"
  },
  {
    "path": "src/samples/sample5.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n// #include \"single_file_lib/rt/rt.h\"\n// #include \"single_file_lib/ui/ui.h\"\n\nconst char* title = \"Sample5\";\n\nstatic ui_view_t left   = ui_view(list);\nstatic ui_view_t right  = ui_view(list);\nstatic ui_view_t bottom = ui_view(stack);\n\n// font scale:\nstatic const fp64_t fs[] = {0.5, 0.75, 1.0, 1.25, 1.50, 1.75, 2.0};\n// font scale index\nstatic int32_t fx = 2; // fs[2] == 1.0\n\nstatic ui_fm_t mf; // mono font\nstatic ui_fm_t pf; // proportional font\n\nstatic ui_edit_view_t edit0;\nstatic ui_edit_view_t edit1;\nstatic ui_edit_view_t edit2;\nstatic ui_edit_doc_t edit_doc_0;\nstatic ui_edit_doc_t edit_doc_1;\nstatic ui_edit_doc_t edit_doc_2;\nstatic ui_edit_view_t* edit[] = { &edit0, &edit1, &edit2 };\nstatic ui_edit_doc_t* doc[] = { &edit_doc_0, &edit_doc_1, &edit_doc_2 };\n\nstatic int32_t focused(void) {\n    // ui_app.focus can point to a button, thus see which edit\n    // control was focused last\n    int32_t ix = -1;\n    for (int32_t i = 0; i < rt_countof(edit) && ix < 0; i++) {\n        if (ui_app.focus == &edit[i]->view) { ix = i; }\n        if (edit[i]->focused) { ix = i; }\n    }\n    static int32_t last_ix = -1;\n    if (ix < 0) { ix = last_ix; }\n    last_ix = ix;\n    return ix;\n}\n\nstatic void focus_back_to_edit(void) {\n    const int32_t ix = focused();\n    if (ix >= 0) {\n        ui_view.set_focus(&edit[ix]->view); // return focus where it was\n    }\n    ui_app.request_layout();\n}\n\nstatic void scaled_fonts(void) {\n    rt_assert(0 <= fx && fx < rt_countof(fs));\n    if (mf.font != null) { ui_gdi.delete_font(mf.font); }\n    int32_t h = (int32_t)(ui_app.fm.mono.normal.height * fs[fx] + 0.5);\n    ui_gdi.update_fm(&mf, ui_gdi.font(ui_app.fm.mono.normal.font, h, -1));\n    if (pf.font != null) { ui_gdi.delete_font(pf.font); }\n    h = (int32_t)(ui_app.fm.prop.normal.height * fs[fx] + 0.5);\n    ui_gdi.update_fm(&pf, ui_gdi.font(ui_app.fm.prop.normal.font, h, -1));\n}\n\nui_button_clicked(full_screen, \"&Full Screen\", 7.0f, {\n    ui_app.full_screen(!ui_app.is_full_screen);\n});\n\nui_button_clicked(quit, \"&Quit\", 7.0f, {\n    if (!ui_fuzzing.from_inside()) { // ignore fuzzer clicks\n        ui_app.close();\n    }\n});\n\nui_button_clicked(fuzz, \"Fu&zz\", 7.0f, {\n    if (!ui_fuzzing.from_inside()) { // ignore fuzzer clicks\n        if (!ui_fuzzing.is_running()) {\n            ui_fuzzing.start(0x1);\n        } else {\n            ui_fuzzing.stop();\n        }\n        fuzz->state.pressed  = ui_fuzzing.is_running();\n        focus_back_to_edit();\n    }\n});\n\nui_toggle_on_off(ro, \"&Read Only\", 7.0f, {\n    if (!ui_fuzzing.from_inside()) { // ignore fuzzer clicks\n        int32_t ix = focused();\n        if (ix >= 0) {\n            edit[ix]->ro = ro->state.pressed;\n    //      rt_println(\"edit[%d].readonly: %d\", ix, edit[ix]->ro);\n            focus_back_to_edit();\n        }\n    }\n});\n\nui_toggle_on_off(ww, \"Hide &Word Wrap\", 7.0f, {\n    int32_t ix = focused();\n    if (ix >= 0) {\n        edit[ix]->hide_word_wrap = ww->state.pressed;\n//      rt_println(\"edit[%d].hide_word_wrap: %d\", ix, edit[ix]->hide_word_wrap);\n        focus_back_to_edit();\n    }\n});\n\n\nui_toggle_on_off(mono, \"&Mono\", 7.0f, {\n    int32_t ix = focused();\n    if (ix >= 0) {\n        ui_edit_view.set_font(edit[ix], mono->state.pressed ? &mf : &pf);\n        focus_back_to_edit();\n    } else {\n        mono->state.pressed = !mono->state.pressed;\n    }\n});\n\nui_toggle_on_off(sl, \"&Single Line\", 7.0f, {\n    if (!ui_fuzzing.from_inside()) { // ignore fuzzer clicks\n        int32_t ix = focused();\n        if (ix == 2) {\n            sl->state.pressed = true; // always single line\n        } else if (0 <= ix && ix < 2) {\n            ui_edit_view_t* e = edit[ix];\n            e->sle = sl->state.pressed;\n    //      rt_println(\"edit[%d].multiline: %d\", ix, e->multiline);\n            if (e->sle) {\n                ui_edit_view.select_all(e);\n                ui_edit_view.replace(e, \"Hello World! Single Line Edit\", -1);\n            }\n            ui_app.request_layout();\n            focus_back_to_edit();\n        }\n    }\n});\n\nstatic void font_plus(void) {\n    if (fx < rt_countof(fs) - 1) {\n        fx++;\n        scaled_fonts();\n        ui_app.request_layout();\n    }\n}\n\nstatic void font_minus(void) {\n    if (fx > 0) {\n        fx--;\n        scaled_fonts();\n        ui_app.request_layout();\n    }\n}\n\nstatic void font_reset(void) {\n    fx = 2;\n    scaled_fonts();\n    ui_app.request_layout();\n}\n\nui_button_clicked(fp, \"Font Ctrl+\", 7.0f, { font_plus(); });\n\nui_button_clicked(fm, \"Font Ctrl-\", 7.0f, { font_minus(); });\n\nstatic ui_label_t label = ui_label(0.0, \"...\");\n\nstatic void set_text(int32_t ix) {\n    static char last[128];\n    ui_view.set_text(&label, \"%d:%d %d:%d %dx%d\\n\"\n        \"scroll %03d:%03d\",\n        edit[ix]->selection.a[0].pn, edit[ix]->selection.a[0].gp,\n        edit[ix]->selection.a[1].pn, edit[ix]->selection.a[1].gp,\n        edit[ix]->view.w, edit[ix]->view.h,\n        edit[ix]->scroll.pn, edit[ix]->scroll.rn);\n    if (0) {\n        rt_println(\"%d:%d %d:%d %dx%d scroll %03d:%03d\",\n            edit[ix]->selection.a[0].pn, edit[ix]->selection.a[0].gp,\n            edit[ix]->selection.a[1].pn, edit[ix]->selection.a[1].gp,\n            edit[ix]->view.w, edit[ix]->view.h,\n            edit[ix]->scroll.pn, edit[ix]->scroll.rn);\n    }\n    // can be called before text.ui initialized\n    if (strcmp(last, ui_view.string(&label)) != 0) {\n        ui_view.invalidate(&label, null);\n    }\n    rt_str_printf(last, \"%s\", ui_view.string(&label));\n}\n\nstatic void paint(ui_view_t* v) {\n    ui_gdi.fill(0, 0, v->w, v->h, ui_colors.black);\n    int32_t ix = focused();\n    for (int32_t i = 0; i < rt_countof(edit); i++) {\n        ui_view_t* e = &edit[i]->view;\n        ui_color_t c = edit[i]->ro ?\n            ui_colors.tone_red : ui_colors.blue;\n        ui_gdi.frame(e->x - 1, e->y - 1, e->w + 2, e->h + 2,\n            i == ix ? c : ui_color_rgb(63, 63, 70));\n    }\n    if (ix >= 0) {\n        set_text(ix);\n    }\n    if (ix >= 0) {\n        ro.state.pressed = edit[ix]->ro;\n        sl.state.pressed = edit[ix]->sle;\n        mono.state.pressed = edit[ix]->view.fm->font == mf.font;\n    }\n}\n\nstatic void open_file(const char* pathname) {\n    char* file = null;\n    int64_t bytes = 0;\n    if (rt_mem.map_ro(pathname, &file, &bytes) == 0) {\n        if (0 < bytes && bytes <= INT64_MAX) {\n            ui_edit_view.select_all(edit[0]);\n            ui_edit_view.replace(edit[0], file, (int32_t)bytes);\n            ui_edit_pg_t start = { .pn = 0, .gp = 0 };\n            ui_edit_view.move(edit[0], start);\n        }\n        rt_mem.unmap(file, bytes);\n    } else {\n        ui_app.toast(5.3, \"\\nFailed to open file \\\"%s\\\".\\n%s\\n\",\n                  pathname, rt_strerr(rt_core.err()));\n    }\n}\n\nstatic void every_100ms(void) {\n//  rt_println(\"\");\n    static ui_view_t* last;\n    if (last != ui_app.focus) { ui_app.request_redraw(); }\n//  last = ui_app.focus;\n}\n\nstatic bool key_pressed(ui_view_t* rt_unused(view), int64_t key) {\n    bool swallow = false;\n    if (ui_app.focused() && key == ui.key.escape) { ui_app.close(); }\n    int32_t ix = focused();\n    if (key == ui.key.f5) {\n        if (ui_app.ctrl && ui_app.shift && !ui_fuzzing.is_running()) {\n            ui_fuzzing.start(0); // on Ctrl+Shift+F5\n        } else if (ui_fuzzing.is_running()) {\n            ui_fuzzing.stop(); // on F5\n        }\n        swallow = true;\n    }\n    if (ui_app.ctrl) {\n        if (key == ui.key.minus) {\n            font_minus();\n            swallow = true;\n        } else if (key == ui.key.plus) {\n            font_plus();\n            swallow = true;\n        } else if (key == '0') {\n            font_reset();\n            swallow = true;\n        }\n    }\n    if (ix >= 0) { set_text(ix); }\n    return swallow;\n}\n\nstatic void edit_enter(ui_edit_view_t* e) {\n    rt_assert(e->sle);\n    if (!ui_app.shift) { // ignore shift ENTER:\n        rt_println(\"text: %.*s\", e->doc->text.ps[0].b, e->doc->text.ps[0].u);\n    }\n}\n\n// see edit.test.c\n\nvoid ui_edit_init_with_lorem_ipsum(ui_edit_text_t* t);\n\nstatic bool can_close(void) {\n    if (!ui_fuzzing.from_inside()) {\n        if (ui_fuzzing.is_running()) { ui_fuzzing.stop(); }\n        return true;\n    } else {\n        return false; // ignore Quit if fuzzing clicked on it\n    }\n}\n\nstatic void opened(void) {\n//  ui_app.view->measure     = measure;\n//  ui_app.view->layout      = layout;\n    ui_app.content->paint       = paint;\n    ui_app.content->key_pressed = key_pressed;\n    scaled_fonts();\n    label.fm = &ui_app.fm.mono.normal;\n    rt_str_printf(fuzz.hint, \"Ctrl+Shift+F5 to start / F5 to stop Fuzzing\");\n    for (int32_t i = 0; i < rt_countof(edit); i++) {\n        ui_edit_doc.init(doc[i], null, 0, false);\n        if (i < 2) {\n            ui_edit_init_with_lorem_ipsum(&doc[i]->text);\n        }\n        ui_edit_view.init(edit[i], doc[i]);\n        edit[i]->view.max_w = ui.infinity;\n        if (i < 2) { edit[i]->view.max_h = ui.infinity; }\n        edit[i]->view.fm = &pf;\n    }\n    ui_app.every_100ms = every_100ms;\n    edit[2]->sle = true;\n    rt_str_printf(edit[0]->view.p.text, \"edit.#0#\");\n    rt_str_printf(edit[1]->view.p.text, \"edit.#1#\");\n    rt_str_printf(edit[2]->view.p.text, \"edit.sle\");\n//  edit[2]->select_all(edit[2]);\n//  edit[2]->paste(edit[2], \"Single line\", -1);\n    ui_edit_view.enter = edit_enter;\n    static ui_view_t span    = ui_view(span);\n    static ui_view_t spacer1 = ui_view(spacer);\n    static ui_view_t spacer2 = ui_view(spacer);\n    ui_view.add(ui_app.content,\n        ui_view.add(&span,\n            ui_view.add(&left,\n                &edit0,\n                &edit1,\n                &label,\n            null),\n            &spacer1,\n            ui_view.add(&right,\n                &full_screen,\n                &quit,\n                &fuzz,\n                &fp,\n                &fm,\n                &mono,\n                &sl,\n                &ro,\n                &ww,\n                &edit2,\n                &spacer2,\n            null),\n        null),\n    null);\n    ui_view_for_each(&right, it, { it->align = ui.align.left; });\n    edit2.view.max_w = ui.infinity;\n    span.max_w = ui.infinity;\n    span.max_h = ui.infinity;\n    label.align = ui.align.left;\n    edit2.view.align = ui.align.left;\n    left.max_w = ui.infinity;\n    left.max_h = ui.infinity;\n    right.max_h = ui.infinity;\n    set_text(0); // need to be two lines for measure\n    ui_view.set_focus(&edit[0]->view);\n    edit0.debug.id = \"#edit0\";\n    edit1.debug.id = \"#edit1\";\n    edit2.debug.id = \"#edit2\";\n    if (rt_args.c > 1) { open_file(rt_args.v[1]); }\n}\n\nstatic void init(void) {\n    ui_app.title = title;\n    ui_app.opened = opened;\n    ui_app.can_close = can_close;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample5\",\n    .dark_mode = true,\n    .init = init,\n    .window_sizing = {\n        .min_w =  3.0f,\n        .min_h =  2.5f,\n        .ini_w =  4.0f,\n        .ini_h =  5.0f\n    }\n};\n\n"
  },
  {
    "path": "src/samples/sample6.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n#include \"stb_image.h\"\n\n// Code in this sample illustrates how to load animated gifs\n// and use them as a movie backdrop and sprites.\n// Sample coed uses ui_app.post() to execute animation steps on the\n// dispatch thread.\n// It also plays mutable MIDI song in a loop.\n// Simple mute button implemented by hand to avoid containers layout\n// logic.\n\nconst char* title = \"Sample6: I am groot\";\n\ntypedef struct animated_gif_s {\n    int32_t bpp; // bytes per pixel\n    int32_t w;\n    int32_t h;\n    int32_t  frames;\n    int32_t* delays; // delays[frames];\n    uint8_t* pixels;\n} animated_gif_t;\n\nenum { max_speed = 3 };\n\ntypedef struct animation_s {\n    animated_gif_t* gif;\n    int32_t  index; // animated_groot index 0..groot.frames - 1\n    uint32_t seed; // for rt_num.random32()\n    int32_t  x;\n    int32_t  y;\n    int32_t  w;\n    int32_t  h;\n    int32_t  speed_x;\n    int32_t  speed_y;\n} animation_t;\n\nstatic animated_gif_t groot;\nstatic animation_t    animated_groot = { .gif = &groot };\n\nstatic animated_gif_t movie;\nstatic animation_t    animated_movie = { .gif = &movie };\n\nstatic rt_thread_t thread; // animated gifs loader thread\n\nstatic bool   muted;\nstatic fp64_t volume; // be mute\n\nstatic ui_midi_t midi;\n\nstatic ui_bitmap_t background;\n\nstatic void init(void);\nstatic void fini(void);\nstatic void character(ui_view_t* view, const char* utf8);\nstatic void stop_and_close(void);\nstatic void open_and_play(void);\n\nstatic void* load_image(const uint8_t* data, int64_t bytes, int32_t* w, int32_t* h,\n    int32_t* bpp, int32_t preferred_bytes_per_pixel);\n\nstatic void* load_animated_gif(const uint8_t* data, int64_t bytes,\n    int32_t** delays, int32_t* w, int32_t* h, int32_t* frames, int32_t* bpp,\n    int32_t preferred_bytes_per_pixel);\n\nstatic const char* midi_file(void);\n\nstatic void paint_groot(animation_t* a) {\n    const animated_gif_t* g = a->gif;\n    const uint8_t* p = g->pixels + g->w * g->h * g->bpp * a->index;\n    ui_bitmap_t frame = { 0 };\n    // alpha blend needs GPu allocated bitmap\n    ui_gdi.bitmap_init(&frame, g->w, g->h, g->bpp, p);\n    const int32_t x = a->x - a->w / 2;\n    const int32_t y = a->y - a->h / 2;\n    ui_gdi.alpha(x, y, a->w, a->h, 0, 0, frame.w, frame.h, &frame, 1.0);\n    ui_gdi.bitmap_dispose(&frame);\n}\n\nstatic void paint_movie(animation_t* a) {\n    ui_gdi.fill(0, 0, ui_app.crc.w, ui_app.crc.h, ui_colors.black);\n    const animated_gif_t* g = a->gif;\n    const uint8_t* p = g->pixels + g->w * g->h * g->bpp * a->index;\n    ui_gdi.pixels(a->x, a->y, a->w, a->h, 0, 0, g->w, g->h,\n                g->w, g->h, g->w * g->bpp, g->bpp, p);\n}\n\nstatic void paint_mute_unmute(ui_view_t* v) {\n    ui_gdi_ta_t ta = ui_gdi.ta.prop.H3;\n    ta.color_id = 0;\n    ta.color = muted ? ui_colors.green : ui_colors.red;\n    #define str_unmuted rt_glyph_mute  \" mute\"\n    #define str_muted rt_glyph_speaker \" unmute\"\n    const int32_t mx = v->x + ui_app.fm.prop.H3.em.w / 16;\n    const int32_t my = v->y + ui_app.fm.prop.H3.em.h / 16;\n    const int32_t mw = ui_app.fm.prop.H3.em.w * 5;\n    const int32_t mh = ui_app.fm.prop.H3.em.h;\n    ui_gdi.rounded(mx, my, mw, mh, (mh / 3) | 0x1,\n                   ta.color, ui_colors.transparent);\n    ui_gdi.text(&ta, ui_app.fm.prop.H3.em.w / 4, 0, \"%s\",\n                muted ? str_muted : str_unmuted);\n}\n\nstatic void paint(ui_view_t* v) {\n    const int32_t w = rt_min(v->w, background.w);\n    const int32_t h = rt_min(v->h, background.h);\n    const int32_t x = (v->w - w) / 2;\n    const int32_t y = (v->h - h) / 2;\n    ui_gdi.bitmap(x, y, w, h, 0, 0, background.w, background.h, &background);\n    if (animated_movie.gif->pixels != null) { paint_movie(&animated_movie); }\n    if (animated_groot.gif->pixels != null) { paint_groot(&animated_groot); }\n    paint_mute_unmute(v);\n}\n\nstatic void character(ui_view_t* rt_unused(v), const char* utf8) {\n    if (utf8[0] == 'q' || utf8[0] == 'Q' || utf8[0] == 033) {\n        ui_app.close();\n    }\n}\n\nstatic bool tap(ui_view_t* rt_unused(v), int32_t ix, bool pressed) {\n    const int32_t w = ui_app.fm.prop.H3.em.w * 5;\n    const int32_t h = ui_app.fm.prop.H3.em.h;\n    const bool inside =\n        0 <= ui_app.mouse.x && ui_app.mouse.x < w &&\n        0 <= ui_app.mouse.y && ui_app.mouse.y < h;\n    const bool swallow = inside && pressed;\n    if (swallow) {\n        muted = !muted;\n        if (muted) {\n            if (ui_midi.is_playing(&midi)) {\n                rt_fatal_if_error(ui_midi.get_volume(&midi, &volume));\n                rt_fatal_if_error(ui_midi.set_volume(&midi,  0));\n            }\n        } else {\n            rt_fatal_if_error(ui_midi.set_volume(&midi,  volume));\n        }\n    }\n    return swallow; // swallows taps inside mute `button`\n}\n\nstatic int64_t notify(ui_midi_t* m, int64_t f) { // f: f\n    rt_swear(&midi == m);\n    #ifdef UI_MIDI_DEBUG\n        if (f & ui_midi.success)    { rt_println(\"success\"); }\n        if (f & ui_midi.failure)    { rt_println(\"failure\"); }\n        if (f & ui_midi.aborted)    { rt_println(\"aborted\"); }\n        if (f & ui_midi.superseded) { rt_println(\"superseded\"); }\n    #endif // UI_MIDI_DEBUG\n    if ((f & (ui_midi.aborted|ui_midi.failure)) != 0) {\n        stop_and_close();\n    } else if ((f & ui_midi.success) != 0) {\n        // success : is received on the end of mini sequence playback\n        // \"when the music over...\" rewind and start playing again\n        rt_fatal_if_error(ui_midi.stop(&midi));\n        rt_fatal_if_error(ui_midi.rewind(&midi));\n        rt_fatal_if_error(ui_midi.play(&midi));\n    }\n    return 0;\n}\n\nstatic void stop_and_close(void) {\n    if (ui_midi.is_open(&midi)) {\n        if (ui_midi.is_playing(&midi)) {\n            midi.notify = null;\n            rt_fatal_if_error(ui_midi.stop(&midi));\n            ui_midi.close(&midi);\n        }\n    }\n}\n\nstatic void open_and_play(void) {\n    if (!ui_midi.is_open(&midi)) {\n        // first call to MIDI Sequencer .open() takes 1.122 seconds\n        // next  attempt to .open() after .close() takes 4.237 seconds!\n        // what can possibly take 1 second on 2GH cpu?\n        rt_fatal_if_error(ui_midi.open(&midi, midi_file()));\n    }\n    if (!ui_midi.is_playing(&midi)) {\n        midi.notify = notify;\n        rt_fatal_if_error(ui_midi.play(&midi));\n        // it is possible that last mute call leaves the volume to 0\n        rt_fatal_if_error(ui_midi.set_volume(&midi, 0.5));\n    }\n}\n\nstatic void delete_midi_file(void) {\n    rt_fatal_if_error(rt_files.unlink(midi_file()));\n}\n\nstatic void load_gif(animated_gif_t* g, const char* name) {\n    void* data = null;\n    int64_t bytes = 0;\n    errno_t r = rt_mem.map_resource(name, &data, &bytes);\n    rt_fatal_if_error(r);\n    // load_animated_gif() calls realloc(delays) w/o first alloc()\n    r = rt_heap.allocate(null, (void**)&g->delays, sizeof(int32_t), false);\n    rt_swear(r == 0 && g->delays != null);\n    g->pixels = load_animated_gif(data, bytes, &g->delays,\n        &g->w, &g->h, &g->frames, &g->bpp, 4);\n    // resources cannot be unmapped do not call rt_mem.unmap()\n}\n\nstatic void load_gifs(void) {\n    load_gif(&movie, \"gotg_gif\");\n    rt_swear(movie.pixels != null &&\n             movie.bpp == 4 &&  movie.frames >= 1,\n             \"%s\", stbi_failure_reason());\n    load_gif(&groot, \"groot_gif\");\n    rt_swear(groot.pixels != null && groot.bpp == 4 && groot.frames >= 1,\n             \"%s\", stbi_failure_reason());\n}\n\nstatic void schedule_next_animation(rt_work_t* work) {\n    animation_t* a = (animation_t*)work->data;\n    rt_swear(0 <= a->index && a->index < a->gif->frames);\n    // milliseconds to seconds:\n    fp64_t ds = a->gif->delays[a->index] * 0.001; // delay in seconds\n    work->when = rt_clock.seconds() + ds;\n    ui_app.post(work);\n}\n\nstatic void dancing_step(rt_work_t* work) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    animation_t* a = (animation_t*)work->data;\n    animated_gif_t* g = (animated_gif_t*)a->gif;\n    int32_t multiplier = 1;\n    while (g->w * multiplier < ui_app.crc.w / 2 &&\n           g->h * multiplier < ui_app.crc.h / 2) {\n        multiplier++;\n    }\n    a->w = g->w * multiplier;\n    a->h = g->h * multiplier;\n    if (a->x < 0 && a->y < 0) {\n        a->x = (ui_app.crc.w - a->w) / 2;\n        a->y = (ui_app.crc.h - a->h) / 2;\n    }\n//  rt_println(\"%d %d speed: %d %d\", a->x, a->y,\n//                                   a->speed_x,\n//                                   a->speed_y);\n    a->index = (a->index + 1) % g->frames;\n    while (a->speed_x == 0) {\n        const uint32_t r = rt_num.random32(&a->seed);\n        a->speed_x = r % (max_speed * 2 + 1) - max_speed;\n    }\n    while (a->speed_y == 0) {\n        const uint32_t r = rt_num.random32(&a->seed);\n        a->speed_y = r % (max_speed * 2 + 1) - max_speed;\n    }\n    a->x += a->speed_x;\n    a->y += a->speed_y;\n    if (a->x - a->w / 2 < 0) {\n        a->x = a->w / 2;\n        a->speed_x = -a->speed_x;\n    } else if (a->x + a->w / 2 >= ui_app.root->w) {\n        a->x = ui_app.root->w - a->w / 2 - 1;\n        a->speed_x = -a->speed_x;\n    }\n    if (a->y - a->h / 2 < 0) {\n        a->y = a->h / 2;\n        a->speed_y = -a->speed_y;\n    } else if (a->y + a->h / 2 >= ui_app.root->h) {\n        a->y = ui_app.root->h - a->h / 2 - 1;\n        a->speed_y = -a->speed_y;\n    }\n    int inc = rt_num.random32(&a->seed) % 2 == 0 ? -1 : +1;\n    if (rt_num.random32(&a->seed) % 2 == 0) {\n        if (1 <= a->speed_x + inc && a->speed_x + inc < max_speed) {\n            a->speed_x += inc;\n        }\n    } else {\n        if (1 <= a->speed_y + inc && a->speed_y + inc < max_speed) {\n            a->speed_y += inc;\n        }\n    }\n    ui_app.request_redraw();\n    schedule_next_animation(work);\n}\n\nstatic void movie_step(rt_work_t* work) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    animation_t* a = (animation_t*)work->data;\n    animated_gif_t* g = (animated_gif_t*)a->gif;\n    int32_t multiplier = 1;\n    while (g->w * multiplier < ui_app.crc.w &&\n           g->h * multiplier < ui_app.crc.h) {\n        multiplier++;\n    }\n    a->w = g->w * multiplier;\n    a->h = g->h * multiplier;\n    a->x = (ui_app.crc.w - a->w) / 2;\n    a->y = (ui_app.crc.h - a->h) / 2;\n    a->index = (a->index + 1) % g->frames;\n    ui_app.request_redraw();\n    schedule_next_animation(work);\n}\n\nstatic void animated_gifs_loader(void* rt_unused(unused)) {\n    ui_cursor_t cursor = ui_app.cursor;\n    ui_app.set_cursor(ui_app.cursors.wait);\n    load_gifs();\n    ui_app.set_cursor(cursor);\n    static rt_work_t dancing = { .work = dancing_step,\n                                 .data = &animated_groot };\n    ui_app.post(&dancing);\n    static rt_work_t cinema  = { .work = movie_step,\n                                 .data = &animated_movie };\n    ui_app.post(&cinema);\n}\n\nstatic void load_png(void) { // from resources\n    void* data = null;\n    int64_t bytes = 0;\n    rt_fatal_if_error(rt_mem.map_resource(\"sample_png\", &data, &bytes));\n    int w = 0;\n    int h = 0;\n    int bpp = 0; // bytes (!) per pixel\n    void* pixels = load_image(data, bytes, &w, &h, &bpp, 0);\n    if (pixels == null) {\n        rt_println(\"%s\", stbi_failure_reason());\n    }\n    rt_not_null(pixels);\n    ui_gdi.bitmap_init(&background, w, h, bpp, pixels);\n    stbi_image_free(pixels);\n}\n\nstatic void start_music(rt_work_t* rt_unused(w)) {\n    open_and_play(); // 1+ second long expensive call\n}\n\nstatic void opened(void) {\n    animated_groot.seed = (uint32_t)rt_clock.nanoseconds();\n    animated_groot.x = -1;\n    animated_groot.y = -1;\n    animated_groot.gif = &groot;\n    thread = rt_thread.start(animated_gifs_loader, null);\n    // start music after first paint() call:\n    static rt_work_t music = { .work = start_music };\n    music.when = rt_clock.seconds() + 0.125;\n    ui_app.post(&music);\n}\n\nstatic void closed(void) {\n    rt_thread.join(thread, -1);\n    if (ui_midi.is_open(&midi)) {\n        // restore pre-muted volume:\n        rt_fatal_if_error(ui_midi.set_volume(&midi,\n                        volume != 0 ? volume : 0.5));\n    }\n    stop_and_close();\n}\n\nstatic void init(void) {\n    ui_app.title = title;\n    ui_app.content->paint     = paint;\n    ui_app.content->character = character;\n    ui_app.content->tap       = tap;\n    ui_app.opened             = opened;\n    ui_app.closed             = closed;\n    load_png();\n}\n\nstatic void fini(void) {\n    ui_gdi.bitmap_dispose(&background);\n    stbi_image_free(groot.pixels);\n    stbi_image_free(groot.delays);\n    stbi_image_free(movie.pixels);\n    stbi_image_free(movie.delays);\n    delete_midi_file();\n}\n\nstatic void* load_image(const uint8_t* data, int64_t bytes,\n        int32_t* w, int32_t* h, int32_t* bpp, int32_t preferred_bpp) {\n    void* pixels = stbi_load_from_memory(data, (int32_t)bytes, w, h,\n        bpp, preferred_bpp);\n    return pixels;\n}\n\nstatic void* load_animated_gif(const uint8_t* data, int64_t bytes,\n        int32_t** delays,\n        int32_t* w, int32_t* h, int32_t* frames, int32_t* bpp,\n        int32_t preferred_bpp) {\n    stbi_uc* pixels = stbi_load_gif_from_memory(data, (int32_t)bytes,\n        delays, w, h, frames, bpp, preferred_bpp);\n    return pixels;\n}\n\nstatic const char* midi_file(void) {\n    // resource -> temporary file unpacking because ancient MIDI Win32 API\n    //             does not support memory buffers (or I didn't find it)\n    static char filename[rt_files_max_path];\n    if (filename[0] == 0) {\n        void* data = null;\n        int64_t bytes = 0;\n        int r = rt_mem.map_resource(\"mr_blue_sky_midi\", &data, &bytes);\n        rt_fatal_if_error(r);\n        rt_fatal_if_error(rt_files.create_tmp(filename,\n                                      rt_countof(filename)));\n        rt_assert(filename[0] != 0);\n        int64_t written = 0;\n        rt_fatal_if_error(rt_files.write_fully(filename, data, bytes,\n                                              &written));\n        rt_assert(written == bytes);\n    }\n    return filename;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample6\",\n    .dark_mode = true,\n    .init = init,\n    .fini = fini,\n    .window_sizing = {\n        .min_w =  4.0f,\n        .min_h =  3.0f,\n        .ini_w =  4.0f,\n        .ini_h =  3.0f\n    }\n};\n"
  },
  {
    "path": "src/samples/sample6.rc",
    "content": "﻿#include \"sample.rc\"\n\ngroot_gif        RCDATA \"groot.gif\" // animated dancing groot\ngotg_gif         RCDATA \"gotg.gif\"  // animated Guardians of The Galaxy 2 movie\nmr_blue_sky_midi RCDATA \"mr_blue_sky.midi\" // free music\n\n// groot.gif made with https://ezgif.com/ ffmpeg and GIMP (re export with Dispose every frame\n// https://tinyurl.com/dancing-baby-groot\n// Mr. Blue Sky Midi: https://freemidi.org/download3-11210-mr-blue-sky-electric-light-orchestra"
  },
  {
    "path": "src/samples/sample7.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n#include <math.h>\n\nconst char* title = \"Sample7 : timers\";\n\nenum { max_count = 1800 };\n\nstatic ui_timer_t timer10ms;\nstatic rt_thread_t thread;\nstatic bool quit;\n\ntypedef struct {\n    fp64_t time[max_count]; // circular buffer of timestamps\n    int pos; // writing position in the buffer for next timer event\n    int samples; // number of samples collected\n    fp64_t dt[max_count]; // delta(t) between 2 timer events\n    fp64_t min_dt;\n    fp64_t max_dt;\n    fp64_t avg;\n    fp64_t spread;\n} time_stats_t;\n\nstatic volatile time_stats_t ts[2];\n\nstatic ui_point_t points[max_count]; // graph polyline coordinates\n\nstatic int32_t N = max_count;\n\nstatic void composed(ui_view_t* view) {\n    if (view->w > 0) { N = rt_min(view->w, N); }\n    rt_println(\"M: %d\", N);\n}\n\nstatic void stats(int32_t ix) {\n    volatile time_stats_t* t = &ts[ix];\n    rt_assert(t->samples >= 2, \"no samples\");\n    int n = rt_min(N, t->samples);\n    t->min_dt = 1.0; // 1 second is 100x of 10ms\n    t->max_dt = 0;\n    int j = 0;\n    fp64_t sum = 0;\n    for (int i = 0; i < n - 1; i++) {\n        int p0 = (t->pos - i - 1 + n) % n;\n        int p1 = (p0 - 1 + n) % n;\n        t->dt[j] = t->time[p0] - (t->time[p1] + 0.01); // expected 10ms\n        t->min_dt = rt_min(t->dt[j], t->min_dt);\n        t->max_dt = rt_max(t->dt[j], t->max_dt);\n        sum += t->time[p0] - t->time[p1];\n        j++;\n    }\n    t->avg = sum / (n - 1);\n    j = 0;\n    fp64_t d0 = fabs(t->min_dt);\n    fp64_t d1 = fabs(t->max_dt);\n    fp64_t spread = rt_max_fp64(d0, d1) * 2;\n    t->spread = rt_max(t->spread, spread);\n//  if (t->samples % 1000 == 0) {\n//      rt_println(\"[%d] samples: %6d spread: %.6f min %.6f max %.6f\",\n//              ix, t->samples, t->spread, t->min_dt, t->max_dt);\n//  }\n}\n\nstatic void print(int32_t *x, int32_t *y, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    *x += ui_gdi.text_va(&ui_gdi.ta.mono.normal, *x, *y, format, va).w;\n    va_end(va);\n}\n\nstatic void println(int32_t *x, int32_t *y, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    *y += ui_gdi.text_va(&ui_gdi.ta.mono.normal, *x, *y, format, va).h;\n    va_end(va);\n}\n\nstatic void graph(ui_view_t* v, int ix, ui_color_t c, int y) {\n    volatile time_stats_t* t = &ts[ix];\n    const int h2 = ui_app.root->h / 2;\n    const int h4 = h2 / 2;\n    const int h8 = h4 / 2;\n    ui_gdi.line(0, y, ui_app.root->w, y, ui_colors.white);\n    if (t->samples > 2) {\n        const fp64_t spread = ts[ix].spread;\n        int n = rt_min(N, t->samples);\n        int j = 0;\n        for (int i = 0; i < n; i++) {\n            points[j].x = n - 1 - i;\n            points[j].y = y - (int32_t)(t->dt[j] * h8 / spread);\n            j++;\n        }\n        ui_gdi.poly(points, n - 1, c);\n        int32_t tx = v->fm->em.w;\n        int32_t ty = y - h8 - v->fm->em.h;\n        println(&tx, &ty, \"min %.3f max %.3f avg %.3f ms  \"\n            \"%.1f sps\",\n            t->min_dt * 1000, t->max_dt * 1000, t->avg, 1 / t->avg);\n    }\n}\n\nstatic void paint(ui_view_t* v) {\n    for (int i = 0; i < rt_countof(ts); i++) {\n        if (ts[i].samples >= 2) { stats(i); }\n    }\n    if (ts[0].spread > 0 && ts[1].spread > 0) {\n        char paint_stats[256];\n        rt_str_printf(paint_stats, \"avg paint time %.1f ms %.1f fps\",\n            ui_app.paint_avg * 1000, ui_app.paint_fps);\n        ui_gdi_ta_t ta = ui_gdi.ta.mono.normal;\n        ta.measure = true;\n        ui_wh_t wh = ui_view.text_metrics(0, 0, false, 0,\n                        &ui_app.fm.mono.normal, \"%s\", paint_stats);\n        int32_t x = v->w - wh.w - v->fm->em.w;\n        int32_t y = v->fm->em.h;\n        print(&x, &y, \"%s\", paint_stats);\n        x = v->fm->em.w;\n        print(&x, &y, \"10ms window timer jitter \");\n        print(&x, &y, \"(\\\"sps\\\" is samples per second)\");\n        const int h2 = ui_app.root->h / 2;\n        const int h4 = h2 / 2;\n        graph(v, 0, ui_colors.tone_red, h4);\n        y = h2;\n        print(&x, &y, \"10ms r/t thread sleep jitter\");\n        graph(v, 1, ui_colors.tone_green, h2 + h4);\n        y = h2 - h4;\n    }\n}\n\nstatic void timer_thread(void* p) {\n    bool* done = (bool*)p;\n    rt_thread.name(\"r/t timer\");\n    rt_thread.realtime();\n    while (!*done) {\n        rt_thread.sleep_for(0.0094);\n        ts[1].time[ts[1].pos] = rt_clock.seconds();\n        ts[1].pos = (ts[1].pos + 1) % N;\n        (ts[1].samples)++;\n        ui_app.request_redraw();\n    }\n}\n\nstatic void timer(ui_view_t* view, ui_timer_t id) {\n    rt_swear(view == ui_app.content);\n    // there are at least 3 timers notifications coming here:\n    // 1 seconds, 100ms and 10ms:\n    if (id == timer10ms) {\n        ts[0].time[ts[0].pos] = ui_app.now;\n        ts[0].pos = (ts[0].pos + 1) % N;\n        (ts[0].samples)++;\n        ui_app.request_redraw();\n    }\n}\n\nstatic void opened(void) {\n    timer10ms = ui_app.set_timer((uintptr_t)&timer10ms, 10);\n    rt_fatal_if(timer10ms == 0);\n    thread = rt_thread.start(timer_thread, &quit);\n    rt_not_null(thread);\n}\n\nstatic void detached_sleep(void* rt_unused(p)) {\n    rt_thread.sleep_for(100.0); // seconds\n}\n\nstatic void detached_loop(void* rt_unused(p)) {\n    uint64_t sum = 0;\n    for (uint64_t i = 0; i < UINT64_MAX; i++) {\n        sum += i;\n    }\n    // making sure that compiler won't get rid of the loop:\n    rt_println(\"%lld\", sum);\n}\n\nstatic void closed(void) {\n    ui_app.kill_timer(timer10ms);\n    quit = true;\n    rt_fatal_if_error(rt_thread.join(thread, -1));\n    thread = null;\n    quit = false;\n    // just to test that ExitProcess(0) works when there is\n    // are detached threads\n    rt_thread_t detached = rt_thread.start(detached_sleep, null);\n    rt_thread.detach(detached);\n    detached = rt_thread.start(detached_loop, null);\n    rt_thread.detach(detached);\n}\n\nstatic void do_not_start_minimized(void) {\n    // This sample does not start minimized but some applications may.\n    if (ui_app.last_visibility != ui.visibility.minimize) {\n        ui_app.visibility = ui_app.last_visibility;\n    } else {\n        ui_app.visibility = ui.visibility.defau1t;\n        ui_app.last_visibility = ui.visibility.defau1t;\n    }\n}\n\nstatic void init(void) {\n    ui_app.title = title;\n    rt_thread.realtime(); // both main thread and timer thread\n    ui_app.closed = closed;\n    ui_app.opened = opened;\n    ui_app.content->timer = timer;\n    ui_app.content->paint = paint;\n    ui_app.content->composed = composed;\n    // no minimize/maximize title bar and system menu\n    ui_app.no_min = true;\n    ui_app.no_max = true;\n    do_not_start_minimized();\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample7\",\n    .init = init,\n    .dark_mode = true,\n    .window_sizing = {\n        .min_w =  9.0f, // 9x5 inches\n        .min_h =  5.0f,\n        .ini_w = 10.0f, // 10x6 inches - fits 11\" laptops\n        .ini_h = 6.0f\n    }\n};\n"
  },
  {
    "path": "src/samples/sample8.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic const char* title = \"Sample8: Panels\";\n\nenum { version = 0x102 };\n\ntypedef rt_begin_packed struct app_data_t {\n    int32_t version;\n    int32_t menu_used;\n    int32_t selected_view;\n    int32_t light;\n    int32_t debug;\n    int32_t large; // show large H3 controls\n    int32_t margins;  // draw controls padding and insets\n    int32_t fm;    // draw controls font metrics\n} rt_end_packed app_data_t;\n\nstatic app_data_t app_data = { .version = version };\n\nstatic void init(void);\nstatic void opened(void);\nstatic void stack_test(ui_view_t* parent);\nstatic void span_test(ui_view_t* parent);\nstatic void list_test(ui_view_t* parent);\nstatic void controls_test(ui_view_t* parent);\nstatic void edit1_test(ui_view_t* parent);\n\nstatic void fini(void) {\n    ui_app.data_save(\"sample8\", &app_data, sizeof(app_data));\n}\n\nstatic void init(void) {\n    app_data_t data = {0};\n    if (ui_app.data_load(\"sample8\", &data, sizeof(data)) == sizeof(data) &&\n        data.version == version) {\n        app_data = data;\n    }\n    ui_app.title  = title;\n    ui_app.fini   = fini;\n    ui_app.opened = opened;\n}\n\nui_app_t ui_app = {\n    .class_name = \"sample8\",\n    .no_decor = true,\n    .no_max   = true, // TODO: should be implied by no_decor\n    .dark_mode = false,\n    .light_mode = false,\n    .init = init,\n    .window_sizing = {\n        .ini_w =  10.0f,\n        .ini_h =   7.0f\n    }\n};\n\nstatic ui_view_t test = ui_view(stack);\n\nstatic ui_view_t tools_list = ui_view(list);\n\nstatic void tools(ui_button_t* b) {\n    rt_println(\"b->state.pressed: %d\", b->state.pressed);\n//  menu is \"flip\" button. It will do this:\n//  b->state.pressed = !b->state.pressed;\n//  automatically before callback.\n    tools_list.state.hidden = !b->state.pressed;\n    app_data.menu_used = 1;\n    ui_app.request_layout();\n}\n\nstatic void switch_view(ui_button_t* b, int32_t ix,\n        void (*build_view)(ui_view_t* v)) {\n    if (!b->state.pressed) {\n        tools_list.state.hidden = true;\n        ui_caption.menu.state.pressed = false;\n        ui_view_for_each(b->parent, c, { c->state.pressed = false; });\n        b->state.pressed = !b->state.pressed;\n        app_data.selected_view = ix;\n        build_view(&test);\n    }\n}\n\nstatic void stack(ui_button_t* b) {\n    switch_view(b, 0, stack_test);\n}\n\nstatic void span(ui_button_t* b) {\n    switch_view(b, 1, span_test);\n}\n\nstatic void list(ui_button_t* b) {\n    switch_view(b, 2, list_test);\n}\n\nstatic void controls(ui_button_t* b) {\n    switch_view(b, 3, controls_test);\n}\n\nstatic void edit1(ui_button_t* b) {\n    switch_view(b, 4, edit1_test);\n}\n\nstatic void debug(ui_button_t* b) {\n    b->state.pressed = !b->state.pressed;\n    app_data.debug = b->state.pressed;\n}\n\nstatic ui_button_t button_bugs =\n        ui_button(rt_glyph_lady_beetle, 0.0f, debug);\n\nstatic ui_mbx_t mbx = ui_mbx( // message box\n    \"Orange frames represent stack, span, or list\\n\"\n    \"components. Green frames indicate padding for\\n\"\n    \"children.\\n\"\n    \"\\n\"\n    \"These insets and padding are intentionally\\n\"\n    \"varied on different sides.\\n\"\n    \"\\n\"\n    \"By default, a container centers its children \\n\"\n    \"unless an alignment is specified by a child.\\n\"\n    \"\\n\"\n    \"When child.max_w = \"\n    rt_glyph_infinity\n    \"or child.max_h = \"\n    rt_glyph_infinity\n    \",\\n\"\n    \"the child expands in the specified direction.\\n\"\n    \"\\n\"\n    \"Span aligns children horizontally, while List\\n\"\n    \"aligns them vertically.\\n\"\n    \"\\n\"\n    \"Overflows are permissible.\\n\"\n    \"\\n\"\n    \"Experiment with resizing the application window.\\n\"\n    \"\\n\"\n    \"Press ESC to close this message.\"\n    \"\\n\",\n    null, null);\n\nstatic void about(ui_button_t* rt_unused(b)) {\n    ui_app.show_toast(&mbx.view, 10.0);\n}\n\nstatic char* nil;\n\nstatic void crash(ui_button_t* rt_unused(b)) {\n    // two random ways to crash in release configuration\n    if (rt_clock.nanoseconds() % 2 == 0) {\n        rt_swear(false, \"should crash in release configuration\");\n    } else {\n        #if 0 // cl.exe compains even with disabled warnings\n        #pragma warning(push)            // this is intentional for testing\n        #pragma warning(disable: 4723)   // potential division by zero\n        int32_t  a[5];\n        int32_t* p = a;\n        rt_println(\"%d\\n\", rt_countof(a));\n        rt_println(\"%d\\n\", rt_countof(p)); // expected \"division by zero\"\n        #pragma warning(pop)\n        #endif\n        (*nil)++; // expected \"access violation\"\n    }\n}\n\nstatic void insert_into_caption(ui_button_t* b, const char* hint) {\n    rt_str_printf(b->hint, \"%s\", hint);\n    b->flat = true;\n    b->padding = (ui_margins_t){0,0,0,0};\n    b->insets  = (ui_margins_t){0,0,0,0};\n    b->align   = ui.align.top;\n    ui_view.add_before(b,  &ui_caption.mini);\n}\n\nstatic void ui_app_root_composed(ui_view_t* rt_unused(v)) {\n    app_data.light = !ui_theme.is_app_dark();\n}\n\nstatic void opened(void) {\n    static ui_view_t list_view = ui_view(list);\n    static ui_view_t span_view = ui_view(span);\n    static ui_button_t button_stack =\n           ui_button(\"&Stack\", 4.25f, stack);\n    static ui_button_t button_span =\n           ui_button(\"&Span\",      4.25f, span);\n    static ui_button_t button_list =\n           ui_button(\"&List\",      4.25f, list);\n    static ui_button_t button_controls =\n           ui_button(\"Con&trols\",  4.25f, controls);\n    static ui_button_t button_edit1 =\n           ui_button(\"Edit &1\",  4.25f, edit1);\n    ui_view.add(ui_app.content,\n        ui_view.add(&list_view,\n            ui_view.add(&span_view,\n                ui_view.add(&tools_list,\n                    &button_stack,\n                    &button_span,\n                    &button_list,\n                    &button_controls,\n                    &button_edit1,\n                null),\n                &test,\n            null),\n        null),\n    null);\n    list_view.max_w = ui.infinity;\n    list_view.max_h = ui.infinity;\n    list_view.insets = (ui_margins_t){ 0, 0, 0, 0 };\n    span_view.max_w = ui.infinity;\n    span_view.max_h = ui.infinity;\n    span_view.insets = (ui_margins_t){ 0, 0, 0, 0 };\n    test.max_w = ui.infinity;\n    test.max_h = ui.infinity;\n    test.color = ui_colors.transparent;\n    test.insets = (ui_margins_t){ 0, 0, 0, 0 };\n    test.background_id = ui_color_id_window;\n    ui_view.set_text(&test, \"%s\", \"test\");\n//  test.paint = ui_view.debug_paint;\n    test.debug.paint.margins = true;\n    // buttons to switch test content\n    tools_list.max_h = ui.infinity;\n    tools_list.color_id = ui_color_id_window;\n    ui_view.set_text(&tools_list, \"%s\", \"Tools\");\n//  tools_list.paint = ui_view.debug_paint;\n    ui_view_for_each(&tools_list, it, {\n        it->align = ui.align.left;\n        it->padding.bottom = 0;\n    });\n    rt_str_printf(button_stack.hint,\n        \"Shows ui_view(stack) layout\\n\"\n        \"Resizing Window will allow\\n\"\n        \"too see how it behaves\");\n    switch (app_data.selected_view) {\n        case  1: span(&button_span);           break;\n        case  2: list(&button_list);           break;\n        case  3: controls(&button_controls);   break;\n        case  4: edit1(&button_edit1);         break;\n        case  0: // drop to default:\n        default: stack(&button_stack);         break;\n    }\n    ui_caption.menu.callback = tools;\n    // 2-state button automatically flip .pressed state:\n    ui_caption.menu.flip = true;\n    ui_caption.icon.state.hidden = true;\n    tools_list.state.hidden = true;\n    if (app_data.menu_used == 0) {\n        ui_app.toast(4.5, rt_glyph_leftward_arrow\n                          \" click \"\n                          rt_glyph_trigram_for_heaven\n                          \" menu button\");\n    }\n    // caption buttons:\n    static ui_button_t button_info =\n           ui_button(rt_glyph_circled_information_source, 0.0f, about);\n    static ui_button_t button_bomb =\n           ui_button(rt_glyph_bomb, 0.0f, crash);\n    insert_into_caption(&button_info, \"About\");\n    insert_into_caption(&button_bugs, \"Debug\");\n    insert_into_caption(&button_bomb, \"Intentionally Crash\");\n    button_info.debug.id = \"#caption.info\";\n    button_bomb.debug.id = \"#caption.bomb\";\n    button_bugs.debug.id = \"#caption.bug\";\n    if (app_data.debug) { debug(&button_bugs); }\n    ui_app.root->composed = ui_app_root_composed;\n    if (!ui_theme.is_app_dark() != app_data.light) {\n        ui_caption.mode.callback(&ui_caption.mode);\n    }\n}\n\nstatic ui_view_t* align(ui_view_t* v, int32_t align) {\n    v->align = align;\n    return v;\n}\n\nstatic void stack_test(ui_view_t* parent) {\n    // TODO: do not need to disband everything just remove children\n    // and switch. list_test() becomes init() like switching views\n    // removing and adding child\n    ui_view.disband(parent);\n    static ui_view_t  stack        = ui_view(stack);\n    static ui_label_t left         = ui_label(0, \" left \");\n    static ui_label_t right        = ui_label(0, \" right \");\n    static ui_label_t top          = ui_label(0, \" top \");\n    static ui_label_t bottom       = ui_label(0, \" bottom \");\n    static ui_label_t left_top     = ui_label(0, \" left|top \");\n    static ui_label_t right_bottom = ui_label(0, \" right|bottom \");\n    static ui_label_t right_top    = ui_label(0, \" right|top \");\n    static ui_label_t left_bottom  = ui_label(0, \" left|bottom \");\n    static ui_label_t center       = ui_label(0, \" center \");\n    stack.insets = (ui_margins_t){ 1.0, 0.5, 0.25, 2.0 };\n    ui_view.add(parent,\n        ui_view.add(&stack,\n            align(&left,         ui.align.left),\n            align(&right,        ui.align.right),\n            align(&top,          ui.align.top),\n            align(&bottom,       ui.align.bottom),\n            align(&left_top,     ui.align.left |ui.align.top),\n            align(&right_bottom, ui.align.right|ui.align.bottom),\n            align(&right_top,    ui.align.right|ui.align.top),\n            align(&left_bottom,  ui.align.left |ui.align.bottom),\n            align(&center,       ui.align.center),\n        null),\n    null);\n    stack.debug.paint.margins = true;\n    stack.max_w  = ui.infinity;\n    stack.max_h  = ui.infinity;\n    stack.insets = (ui_margins_t){ 1.0, 0.5, 0.25, 2.0 };\n    stack.background_id = ui_color_id_window;\n    ui_view.set_text(&stack, \"#stack\");\n    ui_view_for_each(&stack, it, {\n        it->debug.paint.margins = true;\n        it->color = ui_colors.onyx;\n//      it->fm    = &ui_app.fm.prop.H1;\n        it->padding = (ui_margins_t){ 2.0, 0.25, 0.5, 1.0 };\n    });\n}\n\nstatic void span_test(ui_view_t* parent) {\n    // TODO: do not need to disband everything just remove children\n    // and switch. list_test() becomes init() like switching views\n    // removing and adding child\n    ui_view.disband(parent);\n    static ui_view_t  span   = ui_view(span);\n    static ui_label_t left   = ui_label(0, \" left \");\n    static ui_label_t right  = ui_label(0, \" right \");\n    static ui_view_t  spacer = ui_view(spacer);\n    static ui_label_t top    = ui_label(0, \" top \");\n    static ui_label_t bottom = ui_label(0, \" bottom \");\n    ui_view.add(parent,\n        ui_view.add(&span,\n            align(&left,   ui.align.center),\n            align(&top,    ui.align.top),\n            align(&spacer, ui.align.center),\n            align(&bottom, ui.align.bottom),\n            align(&right,  ui.align.center),\n        null),\n    null);\n    span.debug.paint.margins = true;\n    span.max_w    = ui.infinity;\n    span.max_h    = ui.infinity;\n    span.insets   = (ui_margins_t){ 1.0, 0.5, 0.25, 2.0 };\n    ui_view.set_text(&span, \"#span\");\n    span.background_id = ui_color_id_window;\n    ui_view_for_each(&span, it, {\n        it->debug.paint.margins = true;\n        it->color   = ui_colors.onyx;\n        it->padding = (ui_margins_t){ 2.0, 0.25, 0.5, 1.0 };\n        it->max_h   = ui.infinity;\n//      it->fm      = &ui_app.fm.prop.H1;\n//      rt_println(\"%s 0x%02X\", it->text, it->align);\n    });\n    top.max_h = 0;\n    bottom.max_h = 0;\n}\n\nstatic void list_test(ui_view_t* parent) {\n    // TODO: do not need to disband everything just remove children\n    // and switch. list_test() becomes init() like switching views\n    // removing and adding child\n    ui_view.disband(parent);\n    static ui_view_t  list         = ui_view(list);\n    static ui_label_t left         = ui_label(0, \" left \");\n    static ui_label_t right        = ui_label(0, \" right \");\n    static ui_view_t  spacer       = ui_view(spacer);\n    static ui_label_t top          = ui_label(0, \" top \");\n    static ui_label_t bottom       = ui_label(0, \" bottom \");\n    ui_view.add(&test,\n        ui_view.add(&list,\n            align(&top,    ui.align.center),\n            align(&left,   ui.align.left),\n            align(&spacer, ui.align.center),\n            align(&right,  ui.align.right),\n            align(&bottom, ui.align.center),\n        null),\n    null);\n    list.debug.paint.margins = true;\n    list.max_w  = ui.infinity;\n    list.max_h  = ui.infinity;\n    list.insets = (ui_margins_t){ 1.0, 0.5, 0.25, 2.0 };\n    list.background_id = ui_color_id_window;\n    ui_view.set_text(&list, \"#list\");\n    ui_view_for_each(&list, it, {\n        it->debug.paint.margins = true;\n        it->color   = ui_colors.onyx;\n        // TODO: labels, buttons etc should define their own default padding != 0\n        it->padding = (ui_margins_t){ 2.0, 0.25, 0.5, 1.0 };\n        it->max_w   = ui.infinity;\n//      it->fm      = &ui_app.fm.prop.H1;\n    });\n    left.max_w = 0;\n    right.max_w = 0;\n}\n\n// controls test\n\nstatic void slider_format(ui_view_t* v) {\n    ui_slider_t* slider = (ui_slider_t*)v;\n    ui_view.set_text(v, \"%s\", rt_str.uint64(slider->value));\n}\n\nstatic void slider_callback(ui_view_t* v) {\n    ui_slider_t* slider = (ui_slider_t*)v;\n    rt_println(\"value: %d\", slider->value);\n}\n\nstatic void controls_set_margins(ui_view_t* v, bool on_off) {\n    ui_view_for_each(v, it, {\n        controls_set_margins(it, on_off);\n        it->debug.paint.margins = on_off;\n    } );\n}\n\nstatic void controls_margins(ui_view_t* v) {\n    controls_set_margins(v->parent->parent->parent, v->state.pressed);\n    ui_app.request_redraw();\n    app_data.margins = v->state.pressed;\n}\n\nstatic void controls_set_fm(ui_view_t* v, bool on_off) {\n    ui_view_for_each(v, it, {\n        controls_set_fm(it, on_off);\n        it->debug.paint.fm = on_off;\n    } );\n}\n\nstatic void controls_fm(ui_view_t* v) {\n    controls_set_fm(v->parent->parent->parent, v->state.pressed);\n    ui_app.request_redraw();\n    app_data.fm = v->state.pressed;\n}\n\nstatic void controls_set_large(ui_view_t* v, bool on_off) {\n    ui_view_for_each(v, it, {\n        controls_set_large(it, on_off);\n        it->fm = on_off ? &ui_app.fm.prop.H1 : &ui_app.fm.prop.normal;\n    });\n}\n\nstatic void controls_large(ui_view_t* v) {\n    controls_set_large(v->parent->parent->parent, v->state.pressed);\n    app_data.large = v->state.pressed;\n    ui_app.request_layout();\n}\n\nstatic void button_pressed(ui_view_t* v) {\n    if (v->shortcut != 0) {\n        rt_println(\"'%c' 0x%02X %d, %s \\\"%s\\\"\",\n            v->shortcut, v->shortcut, v->shortcut,\n            ui_view_debug_id(v), v->p.text);\n    } else {\n        rt_println(\"%s \\\"%s\\\"\", ui_view_debug_id(v), v->p.text);\n    }\n}\n\nstatic void controls_test(ui_view_t* parent) {\n    #define wild_string                                 \\\n        \"A\"  rt_glyph_zwsp                              \\\n        rt_glyph_combining_enclosing_circle             \\\n        \"B\" rt_glyph_box_drawings_light_diagonal_cross  \\\n        rt_glyph_E_with_cedilla_and_breve\n    // TODO: do not need to disband everything just remove children\n    // and switch. list_test() becomes init() like switching views\n    // removing and adding child\n    ui_view.disband(parent);\n    static ui_view_t   list    = ui_view(list);\n    static ui_view_t   span    = ui_view(span);\n    // horizontal inside span\n    static ui_toggle_t large   = ui_toggle(\"&Large\", 3.0f, controls_large);\n    static ui_label_t  left    = ui_label(0, \"Left\");\n    static ui_button_t button1 = ui_button(\"&Button\", 0, button_pressed);\n    static ui_label_t  right   = ui_label(0, \"Right\");\n    static ui_slider_t slider1 = ui_slider(\"%d\", 6.0f, 0, UINT16_MAX,\n                                           slider_format, slider_callback);\n    static ui_toggle_t toggle1 = ui_toggle(\"Toggle: ___\", 4.0f, null);\n    static ui_button_t egypt   = ui_button(\"\\xC3\\x84\\x67\\x79\\x70\\x74\\x65\\x6E\",\n                                           0, null);\n    static ui_label_t  wild    = ui_label(1, wild_string);\n    // vertical inside list\n    #define min_w_in_em (8.5f)\n    static ui_label_t  label   = ui_label(min_w_in_em, \"Label\");\n    static ui_button_t button2 = ui_button(\"Button\", min_w_in_em, null);\n    static ui_slider_t slider2 = ui_slider(\"%d\", min_w_in_em, 0, UINT16_MAX,\n                                            slider_format, slider_callback);\n    static ui_slider_t slider3 = ui_slider(\"%d\", min_w_in_em, 0, UINT16_MAX,\n                                            slider_format, slider_callback);\n    static ui_toggle_t margins    = ui_toggle(\"&margins\", min_w_in_em,\n                                            controls_margins);\n    static ui_toggle_t fm      = ui_toggle(\"&Font Metrics\", min_w_in_em,\n                                            controls_fm);\n    static ui_view_t   spacer  = ui_view(spacer);\n    ui_view.add(&test,\n        ui_view.add(&list,\n            ui_view.add(&span,\n                align(&large,        ui.align.top),\n                align(&left,         ui.align.top),\n                align(&button1,      ui.align.top),\n                align(&right,        ui.align.top),\n                align(&slider1.view, ui.align.top),\n                align(&toggle1,      ui.align.top),\n                align(&egypt,        ui.align.top),\n                align(&wild,         ui.align.top),\n            null),\n            align(&label,        ui.align.left),\n            align(&button2,      ui.align.left),\n            align(&slider2.view, ui.align.left),\n            align(&slider3.view, ui.align.left),\n            align(&margins,      ui.align.left),\n            align(&fm,           ui.align.left),\n            align(&spacer,       ui.align.left),\n        null),\n    null);\n    list.debug.paint.margins = true;\n    span.align = ui.align.left;\n    list.max_w  = ui.infinity;\n    list.max_h  = ui.infinity;\n    ui_view.set_text(&list, \"#list\");\n    list.background_id = ui_color_id_window;\n    slider2.dec.state.hidden = true;\n    slider2.inc.state.hidden = true;\n    fm.state.pressed = app_data.fm;\n    controls_fm(&fm);\n    margins.state.pressed = app_data.margins;\n    controls_margins(&margins);\n    large.state.pressed = app_data.large;\n    controls_large(&large);\n    toggle1.debug.id = \"#toggle.1\";\n    slider1.debug.id = \"#slider.1\";\n//  slider1.debug.trace.mt = true;\n//  slider1.inc.debug.trace.mt = true;\n//  slider1.dec.debug.trace.mt = true;\n    slider1.inc.debug.id = \"#slider.1.inc\";\n    slider1.dec.debug.id = \"#slider.1.dec\";\n}\n\n// edit1 test\n\nstatic void edit1_test(ui_view_t* parent) {\n    // TODO: do not need to disband everything just remove children\n    // and switch. list_test() becomes init() like switching views\n    // removing and adding child\n    ui_view.disband(parent);\n    static void* text;\n    static int64_t bytes;\n    if (text == null) {\n        if (rt_args.c > 1) {\n            if (rt_files.exists(rt_args.v[1])) {\n                errno_t r = rt_mem.map_ro(rt_args.v[1], &text, &bytes);\n                if (r != 0) {\n                    rt_println(\"rt_mem.map_ro(%s) failed %s\", rt_args.v[1], rt_str.error(r));\n                }\n            } else {\n                rt_println(\"file \\\"%s\\\" does not exist\", rt_args.v[1]);\n            }\n        }\n    }\n    static ui_view_t list = ui_view(list);\n    static ui_edit_view_t edit = {0};\n    static ui_edit_doc_t doc = {0};\n    if (doc.text.np == 0) {\n        rt_swear(ui_edit_doc.init(&doc, text, (int32_t)bytes, false));\n        ui_edit_view.init(&edit, &doc);\n    }\n    ui_view.add(&test,\n        ui_view.add(&list,\n            &edit.view,\n        null),\n    null);\n    list.max_w      = ui.infinity;\n    list.max_h      = ui.infinity;\n    edit.view.fm    = &ui_app.fm.mono.normal;\n    edit.view.max_w = ui.infinity;\n    edit.view.max_h = ui.infinity;\n//  edit.view.debug.paint.margins = true;\n//  edit.view.debug.trace.prc = true;\n    rt_str_printf(edit.p.text, \"#edit\");\n    ui_view.set_focus(&edit.view);\n}\n"
  },
  {
    "path": "src/samples/sample9.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"single_file_lib/rt/rt.h\"\n#include \"single_file_lib/ui/ui.h\"\n#include \"i18n.h\"\n\n#define TITLE \"Sample9\"\n\nstatic void init(void);\n\nui_app_t ui_app = {\n    .class_name = \"sample9\",\n    .init = init,\n    .dark_mode = true,\n    .window_sizing = {\n        .min_w =  9.0f,\n        .min_h =  5.5f,\n        .ini_w = 10.0f,\n        .ini_h =  6.0f\n    }\n};\n\nstatic int32_t panel_border = 1;\nstatic int32_t frame_border = 1;\n\nstatic ui_bitmap_t image;\nstatic uint32_t pixels[1024][1024];\n\nstatic fp64_t zoom = 0.5;\nstatic fp64_t sx = 0.25; // [0..1]\nstatic fp64_t sy = 0.25; // [0..1]\n\nstatic struct { fp64_t x; fp64_t y; } stack[52];\nstatic int top = 1; // because it is already zoomed in once above\n\nstatic ui_slider_t zoomer;\n\nstatic ui_label_t toast_filename = ui_label(0.0, \"filename placeholder\");\n\nstatic ui_label_t label_single_line = ui_label(0.0, \"Mandelbrot Explorer\");\n\nstatic ui_label_t label_multiline = ui_label(19.0,\n    \"Click inside or +/- to zoom;\\n\"\n    \"right mouse click to zoom out;\\n\"\n    \"use touchpad or keyboard \"\n    rt_glyph_leftwards_white_arrow rt_glyph_upwards_white_arrow\n    rt_glyph_downwards_white_arrow rt_glyph_rightwards_white_arrow\n    \" to pan\");\n\nstatic ui_label_t about = ui_label(34.56f,\n    \"\\nClick inside Mandelbrot Julia Set fractal to zoom in into interesting \"\n    \"areas. Right mouse click to zoom out.\\n\"\n    \"Use Win + Shift + S to take a screenshot of something \"\n    \"beautiful that caught your eye.\"\n    \"\\n\\n\"\n    \"This sample also a showcase of controls like toggle, message box, \"\n    \"tooltips, clipboard copy, full screen switching, open file \"\n    \"dialog and on-the-fly locale switching for simple and possibly \"\n    \"incorrect Simplified Chinese localization.\"\n    \"\\n\\n\"\n    \"Press ESC or click the \"\n    rt_glyph_multiplication_sign\n    \" button in right top corner \"\n    \"to dismiss this message or just wait - it will disappear by \"\n    \"itself in 10 seconds.\\n\");\n\n#ifdef SAMPLE9_USE_STATIC_UI_VIEW_MACROS\n\nui_mbx_chosen(mbx, // message box\n    \"\\\"Pneumonoultramicroscopicsilicovolcanoconiosis\\\"\\n\"\n    \"is it the longest English language word or not?\", {\n    rt_println(\"option=%d\", option); // -1 or index of { \"&Yes\", \"&No\" }\n}, \"&Yes\", \"&No\");\n\n#else\n\nstatic void mbx_callback(ui_view_t* v) {\n    ui_mbx_t* mbx = (ui_mbx_t*)v;\n    rt_assert(-1 <= mbx->option && mbx->option < 2);\n    static const char* name[] = { \"Cancel\", \"Yes\", \"No\" };\n    rt_println(\"option: %d \\\"%s\\\"\", mbx->option, name[mbx->option + 1]);\n}\n\nstatic ui_mbx_t mbx = ui_mbx( // message box\n    \"\\\"Pneumonoultramicroscopicsilicovolcanoconiosis\\\"\\n\"\n    \"is it the longest English language word or not?\", mbx_callback,\n    \"&Yes\", \"&No\");\n\n#endif\n\nstatic const char* filter[] = {\n    \"All Files\", \"*\",\n    \"Image Files\", \"*.png;*.jpg\",\n    \"Text Files\", \"*.txt;*.doc;*.ini\",\n    \"Executables\", \"*.exe\"\n};\n\nstatic void open_file(ui_button_t* rt_unused(b)) {\n    const char* home = rt_files.known_folder(rt_files.folder.home);\n    //  all files filer: null, 0\n    const char* fn = ui_app.open_file(home, filter, rt_countof(filter));\n    if (fn[0] != 0) {\n        ui_view.set_text(&toast_filename, \"\\n%s\\n\", fn);\n        rt_println(\"\\\"%s\\\"\", fn);\n        ui_app.show_toast(&toast_filename, 3.3);\n    }\n}\n\nui_button_t button_open_file = ui_button(\"&Open\", 7.5, open_file);\n\nstatic void flip_full_clicked(ui_button_t* b) {\n    b->state.pressed = !b->state.pressed;\n    ui_app.full_screen(b->state.pressed);\n    if (b->state.pressed) {\n        ui_app.toast(1.75, \"Press ESC to exit full screen\");\n    }\n}\n\nstatic ui_button_t button_full_screen = ui_button(\n        rt_glyph_square_four_corners, 1, flip_full_clicked);\n\nstatic void flip_locale(ui_button_t* b) {\n    b->state.pressed = !b->state.pressed;\n    rt_fatal_if_error(rt_nls.set_locale(b->state.pressed ? \"zh-CN\" : \"en-US\"));\n    ui_app.request_layout(); // because center panel layout changed\n}\n\nstatic ui_button_t button_locale = ui_button(\n    rt_glyph_kanji_onna_female \"A\", 1, flip_locale);\n\nstatic void about_clicked(ui_button_t* rt_unused(b)) {\n    ui_app.show_toast(&about, 10.0);\n}\n\nstatic ui_button_t button_about = ui_button(\"&About\", 7.5, about_clicked);\n\nui_button_clicked(button_mbx, \"&Message Box\", 7.5, {\n    ui_app.show_toast(&mbx.view, 0);\n});\n\n\nstatic void scroll_toggle(ui_button_t* rt_unused(b)) {\n    ui_app.request_redraw();\n}\n\nstatic ui_toggle_t scroll = ui_toggle(\"Scroll &Direction:\",\n                                      /* min_w_em: */ 3.0f,\n                              /* callback:*/ scroll_toggle);\n\nstatic ui_view_t panel_top    = ui_view(stack);\nstatic ui_view_t panel_bottom = ui_view(stack);\nstatic ui_view_t panel_center = ui_view(stack);\nstatic ui_view_t panel_right  = ui_view(stack);\n\nstatic const ui_gdi_ta_t* ta = &ui_gdi.ta.prop.normal;\n\nstatic void print(int32_t *x, int32_t *y, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    *x += ui_gdi.text_va(ta, *x, *y, format, va).w;\n    va_end(va);\n}\n\nstatic void println(int32_t *x, int32_t *y, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    *y += ui_gdi.text_va(ta, *x, *y, format, va).h;\n    va_end(va);\n}\n\nstatic void after(ui_view_t* v, const char* format, ...) {\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    int32_t x = v->x + v->w + v->fm->em.w;\n    int32_t y = v->y + insets.top;\n    va_list va;\n    va_start(va, format);\n    ui_gdi.text_va(ta, x, y, format, va);\n    va_end(va);\n}\n\nstatic void panel_paint(ui_view_t* v) {\n    if (v->color == ui_colors.transparent) {\n        v->color = ui_app.content->color;\n    }\n    ui_gdi.fill(v->x, v->y, v->w, v->h, ui_color_rgb(30, 30, 30));\n    ui_color_t c = ui_color_rgb(63, 63, 70);\n    if (v == &panel_right) {\n        ui_gdi.line(v->x, v->y, v->x + v->w, v->y, c);\n        ui_gdi.line(v->x + v->w, v->y, v->x + v->w, v->y + v->h, c);\n        ui_gdi.line(v->x + v->w, v->y + v->h, v->x, v->y + v->h, c);\n        ui_gdi.line(v->x, v->y + v->h, v->x, v->y, c);\n    } else if (v == &panel_top || v == &panel_bottom) {\n        ui_gdi.line(v->x, v->y, v->x, v->y + v->h, c);\n        ui_gdi.line(v->x, v->y + v->h, v->x + v->w, v->y + v->h, c);\n        ui_gdi.line(v->x + v->w, v->y, v->x, v->y, c);\n    } else {\n        rt_assert(v == &panel_center);\n        ui_gdi.line(v->x, v->y, v->x, v->y + v->h, c);\n    }\n    int32_t x = v->x + panel_border + rt_max(1, v->fm->em.w / 8);\n    int32_t y = v->y + panel_border + rt_max(1, v->fm->em.h / 4);\n    const int32_t radius = (v->fm->em.h / 4) | 0x1;\n    ui_gdi.rounded(x, y, v->fm->em.w * 12, v->fm->em.h,\n           radius,  v->color, ui_colors.transparent);\n    x = v->x + panel_border + rt_max(1, v->fm->em.w / 2);\n    y = v->y + panel_border + rt_max(1, v->fm->em.h / 4);\n    ui_gdi.text(ta, x, y, \"%d,%d %dx%d %s\",\n                     v->x, v->y, v->w, v->h, ui_view.string(v));\n}\n\nstatic void right_layout(ui_view_t* v) {\n    int x = v->x + v->fm->em.w;\n    int y = v->y + v->fm->em.h * 2;\n    ui_view_for_each(v, c, {\n        c->x = x;\n        c->y = y;\n        y += c->h + v->fm->em.h / 2;\n    });\n}\n\nstatic void right_paint(ui_view_t* v) {\n    panel_paint(v);\n    const ui_gdi_ta_t* restore = ta;\n    after(&button_locale, \"&Locale %s\", button_locale.state.pressed ?\n        \"zh-CN\" : \"en-US\");\n    after(&button_full_screen, \"%s\",\n        ui_app.is_full_screen ?\n        rt_nls.str(\"Restore from &Full Screen\") :\n        rt_nls.str(\"&Full Screen\"));\n    int32_t x = label_multiline.x;\n    int32_t y = label_multiline.y + label_multiline.h + v->fm->em.h / 4;\n//  rt_println(\"%d,%d %dx%d\",\n//      label_multiline.x,\n//      label_multiline.y,\n//      label_multiline.w,\n//      label_multiline.h\n//  );\n\n    println(&x, &y, \"%s\", rt_nls.str(\"Proportional\"));\n    ta = &ui_gdi.ta.mono.normal;\n    println(&x, &y, \"%s\", rt_nls.str(\"Monospaced\"));\n    ta = &ui_gdi.ta.prop.H1;\n    println(&x, &y, \"H1 %s\", rt_nls.str(\"Header\"));\n    ta = &ui_gdi.ta.prop.H2;\n    println(&x, &y, \"H2 %s\", rt_nls.str(\"Header\"));\n    ta = &ui_gdi.ta.prop.H3;\n    println(&x, &y, \"H3 %s\", rt_nls.str(\"Header\"));\n    ta = &ui_gdi.ta.prop.normal;\n    println(&x, &y, \"%s %dx%d root: %d,%d %dx%d\", rt_nls.str(\"Client area\"),\n            ui_app.crc.w, ui_app.crc.h,\n            ui_app.root->x, ui_app.root->y,\n            ui_app.root->w, ui_app.root->h);\n    println(&x, &y, \"%s %dx%d dpi: %d\", rt_nls.str(\"Window\"),\n            ui_app.wrc.w, ui_app.wrc.h, ui_app.dpi.window);\n    println(&x, &y, \"%s %dx%d dpi: %d ang %d raw %d\",\n            rt_nls.str(\"Monitor\"),\n            ui_app.mrc.w, ui_app.mrc.h,\n            ui_app.dpi.monitor_effective,\n            ui_app.dpi.monitor_angular,\n            ui_app.dpi.monitor_raw);\n    println(&x, &y, \"%s %d %d\", rt_nls.str(\"Left Top\"),\n            ui_app.wrc.x, ui_app.wrc.y);\n    println(&x, &y, \"%s %d %d\", rt_nls.str(\"Mouse\"),\n            ui_app.mouse.x, ui_app.mouse.y);\n    println(&x, &y, \"%d x paint()\", ui_app.paint_count);\n    println(&x, &y, \"%.1fms (%s %.1f %s %.1f)\",\n            ui_app.paint_time * 1000.0,\n            rt_nls.str(\"max\"), ui_app.paint_max * 1000.0,\n            rt_nls.str(\"avg\"), ui_app.paint_avg * 1000.0);\n    after(&zoomer.view, \"%.16f\", zoom);\n    after(&scroll, \"%s\", scroll.state.pressed ?\n        rt_nls.str(\"Natural\") : rt_nls.str(\"Reverse\"));\n    ta = restore;\n}\n\nstatic void center_paint(ui_view_t* view) {\n//  ui_gdi.set_clip(view->x, view->y, view->w, view->h);\n    ui_gdi.fill(view->x, view->y, view->w, view->h, ui_colors.black);\n    int x = (view->w - image.w) / 2;\n    int y = (view->h - image.h) / 2;\n//  ui_gdi.alpha(view->x + x, view->y + y, image.w, image.h, &image, 0.5);\n    ui_gdi.bitmap(view->x + x, view->y + y, image.w, image.h,\n                 0, 0, image.w, image.h, &image);\n//  ui_gdi.set_clip(0, 0, 0, 0);\n}\n\nstatic void measure(ui_view_t* v) {\n    v->fm = &ui_app.fm.mono.normal;\n    panel_border = rt_max(1, v->fm->em.h / 4);\n    frame_border = rt_max(1, v->fm->em.h / 8);\n    rt_assert(panel_border > 0 && frame_border > 0);\n    const int32_t w = ui_app.root->w;\n    const int32_t h = ui_app.root->h;\n    // measure ui elements\n    panel_top.w = (int32_t)(0.70 * w);\n    panel_top.h = v->fm->em.h * 2;\n    panel_bottom.w = panel_top.w;\n    panel_bottom.h = v->fm->em.h * 2;\n    panel_right.w = w - panel_bottom.w;\n    panel_right.h = h;\n    panel_center.w = panel_bottom.w;\n    panel_center.h = h - panel_bottom.h - panel_top.h;\n}\n\nstatic void layout(ui_view_t* rt_unused(view)) {\n    rt_assert(view->fm->em.w > 0 && view->fm->em.h > 0);\n    const int32_t h = ui_app.root->h;\n    panel_top.x = 0;\n    panel_top.y = 0;\n    panel_bottom.x = 0;\n    panel_bottom.y = h - panel_bottom.h;\n    panel_right.x = panel_bottom.w;\n    panel_right.y = 0;\n    panel_center.x = 0;\n    panel_center.y = panel_top.h;\n}\n\nstatic void refresh(void);\n\nstatic void zoom_out(void) {\n    rt_assert(top > 0);\n    top--;\n    sx = stack[top].x;\n    sy = stack[top].y;\n    zoom *= 2;\n}\n\nstatic void zoom_in(int x, int y) {\n    rt_assert(top < rt_countof(stack));\n    stack[top].x = sx;\n    stack[top].y = sy;\n    top++;\n    zoom /= 2;\n    sx += zoom * x / image.w;\n    sy += zoom * y / image.h;\n}\n\nstatic bool tap(ui_view_t* rt_unused(v), int32_t ix, bool pressed) {\n    const bool inside = ui_view.inside(&panel_center, &ui_app.mouse);\n    if (pressed && inside) {\n        int x = ui_app.mouse.x - (panel_center.w - image.w) / 2 - panel_center.x;\n        int y = ui_app.mouse.y - (panel_center.h - image.h) / 2 - panel_center.y;\n        if (0 <= x && x < image.w && 0 <= y && y < image.h) {\n            if (pressed && ix == 2) {\n                if (zoom < 1) { zoom_out(); refresh(); }\n            } else if (pressed && ix == 0) {\n                if (top < rt_countof(stack)) { zoom_in(x, y); refresh(); }\n            }\n        }\n        ui_app.request_redraw();\n    }\n    return pressed && inside;\n}\n\nstatic void slider_format(ui_view_t* v) {\n    ui_slider_t* slider = (ui_slider_t*)v;\n    ui_view.set_text(v, \"%s\", rt_str.uint64(slider->value));\n}\n\nstatic void zoomer_callback(ui_view_t* v) {\n    ui_slider_t* slider = (ui_slider_t*)v;\n    fp64_t z = 1;\n    for (int i = 0; i < slider->value; i++) { z /= 2; }\n    while (zoom > z) { zoom_in(image.w / 2, image.h / 2); }\n    while (zoom < z) { zoom_out(); }\n    refresh();\n}\n\nstatic void mouse_scroll(ui_view_t* unused, ui_point_t dx_dy) {\n    (void)unused;\n    if (!scroll.state.pressed) { dx_dy.y = -dx_dy.y; }\n    if (!scroll.state.pressed) { dx_dy.x = -dx_dy.x; }\n    sx = sx + zoom * dx_dy.x / image.w;\n    sy = sy + zoom * dx_dy.y / image.h;\n    refresh();\n}\n\nstatic void character(ui_view_t* view, const char* utf8) {\n    char ch = utf8[0];\n    if (ch == 'q' || ch == 'Q') {\n        ui_app.close();\n    } else if (ch == 033 && ui_app.is_full_screen) {\n        flip_full_clicked(&button_full_screen);\n    } else if (ch == '+' || ch == '=') {\n        zoom /= 2; refresh();\n    } else if (ch == '-' || ch == '_') {\n        zoom = rt_min(zoom * 2, 1.0); refresh();\n    } else if (ch == '<' || ch == ',') {\n        ui_point_t pt = {+image.w / 8, 0};\n        mouse_scroll(view, pt);\n    } else if (ch == '>' || ch == '.') {\n        ui_point_t pt = {-image.w / 8, 0};\n        mouse_scroll(view, pt);\n    } else if (ch == 3) { // Ctrl+C\n        rt_clipboard.put_image(&image);\n    }\n}\n\nstatic bool keyboard(ui_view_t* view, int64_t vk) {\n    bool swallow = true;\n    if (vk == ui.key.up) {\n        ui_point_t pt = {0, +image.h / 8};\n        mouse_scroll(view, pt);\n    } else if (vk == ui.key.down) {\n        ui_point_t pt = {0, -image.h / 8};\n        mouse_scroll(view, pt);\n    } else if (vk == ui.key.left) {\n        ui_point_t pt = {+image.w / 8, 0};\n        mouse_scroll(view, pt);\n    } else if (vk == ui.key.right) {\n        ui_point_t pt = {-image.w / 8, 0};\n        mouse_scroll(view, pt);\n    } else {\n        swallow = false;\n    }\n    return swallow;\n}\n\nstatic void init_panel(ui_view_t* panel, const char* text, ui_color_t color,\n        void (*paint)(ui_view_t*)) {\n    ui_view.set_text(panel, \"%s\", text);\n    panel->color = color;\n    panel->paint = paint;\n}\n\nstatic void opened(void) {\n    ui_app.content->measure      = measure;\n    ui_app.content->layout       = layout;\n    ui_app.content->character    = character;\n    ui_app.content->key_pressed  = keyboard; // virtual_keys\n    ui_app.content->mouse_scroll = mouse_scroll;\n    panel_center.tap = tap;\n    int n = rt_countof(pixels);\n    static_assert(sizeof(pixels[0][0]) == 4, \"4 bytes per pixel\");\n    static_assert(rt_countof(pixels) == rt_countof(pixels[0]), \"square\");\n    ui_gdi.bitmap_init(&image, n, n, (int32_t)sizeof(pixels[0][0]), (uint8_t*)pixels);\n    init_panel(&panel_top,    \"top\",    ui_colors.orange, panel_paint);\n    init_panel(&panel_center, \"center\", ui_colors.white, center_paint);\n    init_panel(&panel_bottom, \"bottom\", ui_colors.tone_blue, panel_paint);\n    init_panel(&panel_right,  \"right\",  ui_colors.tone_green, right_paint);\n    panel_right.layout = right_layout;\n    label_single_line.highlightable = true;\n    label_single_line.flat = true;\n    label_multiline.highlightable = true;\n    rt_str_printf(label_multiline.hint, \"%s\",\n        \"Ctrl+C or Right Mouse click to copy text to clipboard\");\n    ui_view.set_text(&label_multiline, \"%s\", rt_nls.string(str_help, \"\"));\n    button_locale.shortcut = 'l';\n    button_full_screen.shortcut = 'f';\n#ifdef SAMPLE9_USE_STATIC_UI_VIEW_MACROS\n    ui_slider_init(&zoomer, \"Zoom: 1 / (2^%d)\", 7.0, 0, rt_countof(stack) - 1,\n        zoomer_callback);\n#else\n    zoomer = (ui_slider_t)ui_slider(\"Zoom: 1 / (2^%d)\", 7.0, 0, rt_countof(stack) - 1,\n        slider_format, zoomer_callback);\n#endif\n    rt_str_printf(button_mbx.hint, \"Show Yes/No message box\");\n    rt_str_printf(button_about.hint, \"Show About message box\");\n    ui_view.add(&panel_right,\n        &button_locale,\n        &button_full_screen,\n        &zoomer,\n        &scroll,\n        &button_open_file,\n        &button_about,\n        &button_mbx,\n        &label_single_line,\n        &label_multiline,\n        null\n    );\n    ui_view.add(ui_app.content,\n        &panel_top,\n        &panel_center,\n        &panel_right,\n        &panel_bottom,\n        null);\n    refresh();\n}\n\nstatic void init(void) {\n    ui_app.title = TITLE;\n    ui_app.opened = opened;\n}\n\nstatic fp64_t scale0to1(int v, int range, fp64_t sh, fp64_t zm) {\n    return sh + zm * v / range;\n}\n\nstatic fp64_t into(fp64_t v, fp64_t lo, fp64_t hi) {\n    rt_assert(0 <= v && v <= 1);\n    return v * (hi - lo) + lo;\n}\n\nstatic void mandelbrot(ui_bitmap_t* im) {\n    for (int r = 0; r < im->h; r++) {\n        fp64_t y0 = into(scale0to1(r, im->h, sy, zoom), -1.12, 1.12);\n        for (int c = 0; c < im->w; c++) {\n            fp64_t x0 = into(scale0to1(c, im->w, sx, zoom), -2.00, 0.47);\n            fp64_t x = 0;\n            fp64_t y = 0;\n            int iteration = 0;\n            enum { max_iteration = 100 };\n            while (x* x + y * y <= 2 * 2 && iteration < max_iteration) {\n                fp64_t t = x * x - y * y + x0;\n                y = 2 * x * y + y0;\n                x = t;\n                iteration++;\n            }\n            static ui_color_t palette[16] = {\n                ui_color_rgb( 66,  30,  15),  ui_color_rgb( 25,   7,  26),\n                ui_color_rgb(  9,   1,  47),  ui_color_rgb(  4,   4,  73),\n                ui_color_rgb(  0,   7, 100),  ui_color_rgb( 12,  44, 138),\n                ui_color_rgb( 24,  82, 177),  ui_color_rgb( 57, 125, 209),\n                ui_color_rgb(134, 181, 229),  ui_color_rgb(211, 236, 248),\n                ui_color_rgb(241, 233, 191),  ui_color_rgb(248, 201,  95),\n                ui_color_rgb(255, 170,   0),  ui_color_rgb(204, 128,   0),\n                ui_color_rgb(153,  87,   0),  ui_color_rgb(106,  52,   3)\n            };\n            ui_color_t color = palette[iteration % rt_countof(palette)];\n            uint8_t* px = &((uint8_t*)im->pixels)[r * im->w * 4 + c * 4];\n            px[3] = 0xFF;\n            px[0] = (color >> 16) & 0xFF;\n            px[1] = (color >>  8) & 0xFF;\n            px[2] = (color >>  0) & 0xFF;\n        }\n    }\n}\n\nstatic void refresh(void) {\n    if (sx < 0) { sx = 0; }\n    if (sx > 1 - zoom) { sx = 1 - zoom; }\n    if (sy < 0) { sy = 0; }\n    if (sy > 1 - zoom) { sy = 1 - zoom; }\n    if (zoom == 1) { sx = 0; sy = 0; }\n    zoomer.value = 0;\n    fp64_t z = 1;\n    while (z != zoom) { zoomer.value++; z /= 2; }\n    zoomer.value = rt_min(zoomer.value, zoomer.value_max);\n    mandelbrot(&image);\n    ui_app.request_redraw();\n}\n\n"
  },
  {
    "path": "src/samples/stb_image.c",
    "content": "#include \"rt/rt.h\"\n\nstatic void* stb_malloc(size_t n) {\n    rt_assert(n > 0);\n    void* a = null;\n    errno_t r = rt_heap.allocate(null, &a, n, false);\n    rt_swear(r == 0 && a != null);\n//  rt_println(\"%p : %8lld\", a, n);\n    return a;\n}\n\nstatic void* stb_realloc(void* p, size_t n) {\n    rt_assert(n > 0);\n    void* a = p;\n    errno_t r = rt_heap.reallocate(null, &a, n, false);\n    rt_swear(r == 0 && a != null);\n//  rt_println(\"%p -> %p : %8lld\", p, a, n);\n    return a;\n}\n\nstatic void* stb_realloc_sized(void* p, size_t rt_unused(s), size_t n) {\n    rt_assert(n > 0);\n    void* a = p;\n    errno_t r = rt_heap.reallocate(null, &a, n, false);\n    rt_swear(r == 0 && a != null);\n//  rt_println(\"%p : %8lld -> %p : %8lld\", p, s, a, n);\n    return a;\n}\n\nstatic void  stb_free(void* p) {\n//  rt_println(\"%p\", p);\n    rt_heap.deallocate(null, p);\n}\n\n#pragma warning(disable: 4459) // declaration of '...' hides global declaration\n#define STBI_ASSERT(...) rt_assert(__VA_ARGS__)\n\n#define STBI_MALLOC(sz)           stb_malloc(sz)\n#define STBI_REALLOC(p,newsz)     stb_realloc((p), (newsz))\n#define STBI_FREE(p)              stb_free(p)\n#define STBI_REALLOC_SIZED(p,oldsz,newsz) stb_realloc_sized((p),(oldsz),(newsz))\n\n#define STB_IMAGE_IMPLEMENTATION\n#include \"stb_image.h\"\n"
  },
  {
    "path": "src/samples/stb_image.h",
    "content": "/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb\n                                  no warranty implied; use at your own risk\n\n   Do this:\n      #define STB_IMAGE_IMPLEMENTATION\n   before you include this file in *one* C or C++ file to create the implementation.\n\n   // i.e. it should look like this:\n   #include ...\n   #include ...\n   #include ...\n   #define STB_IMAGE_IMPLEMENTATION\n   #include \"stb_image.h\"\n\n   You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.\n   And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free\n\n\n   QUICK NOTES:\n      Primarily of interest to game developers and other people who can\n          avoid problematic images and only need the trivial interface\n\n      JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)\n      PNG 1/2/4/8/16-bit-per-channel\n\n      TGA (not sure what subset, if a subset)\n      BMP non-1bpp, non-RLE\n      PSD (composited view only, no extra channels, 8/16 bit-per-channel)\n\n      GIF (*comp always reports as 4-channel)\n      HDR (radiance rgbE format)\n      PIC (Softimage PIC)\n      PNM (PPM and PGM binary only)\n\n      Animated GIF still needs a proper API, but here's one way to do it:\n          http://gist.github.com/urraka/685d9a6340b26b830d49\n\n      - decode from memory or through FILE (define STBI_NO_STDIO to remove code)\n      - decode from arbitrary I/O callbacks\n      - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)\n\n   Full documentation under \"DOCUMENTATION\" below.\n\n\nLICENSE\n\n  See end of file for license information.\n\nRECENT REVISION HISTORY:\n\n      2.29  (2023-05-xx) optimizations\n      2.28  (2023-01-29) many error fixes, security errors, just tons of stuff\n      2.27  (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes\n      2.26  (2020-07-13) many minor fixes\n      2.25  (2020-02-02) fix warnings\n      2.24  (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically\n      2.23  (2019-08-11) fix clang static analysis warning\n      2.22  (2019-03-04) gif fixes, fix warnings\n      2.21  (2019-02-25) fix typo in comment\n      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs\n      2.19  (2018-02-11) fix warning\n      2.18  (2018-01-30) fix warnings\n      2.17  (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings\n      2.16  (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes\n      2.15  (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC\n      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs\n      2.13  (2016-12-04) experimental 16-bit API, only for PNG so far; fixes\n      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes\n      2.11  (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64\n                         RGB-format JPEG; remove white matting in PSD;\n                         allocate large structures on the stack;\n                         correct channel count for PNG & BMP\n      2.10  (2016-01-22) avoid warning introduced in 2.09\n      2.09  (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED\n\n   See end of file for full revision history.\n\n\n ============================    Contributors    =========================\n\n Image formats                          Extensions, features\n    Sean Barrett (jpeg, png, bmp)          Jetro Lauha (stbi_info)\n    Nicolas Schulz (hdr, psd)              Martin \"SpartanJ\" Golini (stbi_info)\n    Jonathan Dummer (tga)                  James \"moose2000\" Brown (iPhone PNG)\n    Jean-Marc Lienher (gif)                Ben \"Disch\" Wenger (io callbacks)\n    Tom Seddon (pic)                       Omar Cornut (1/2/4-bit PNG)\n    Thatcher Ulrich (psd)                  Nicolas Guillemot (vertical flip)\n    Ken Miller (pgm, ppm)                  Richard Mitton (16-bit PSD)\n    github:urraka (animated gif)           Junggon Kim (PNM comments)\n    Christopher Forseth (animated gif)     Daniel Gibson (16-bit TGA)\n                                           socks-the-fox (16-bit PNG)\n                                           Jeremy Sawicki (handle all ImageNet JPGs)\n Optimizations & bugfixes                  Mikhail Morozov (1-bit BMP)\n    Fabian \"ryg\" Giesen                    Anael Seghezzi (is-16-bit query)\n    Arseny Kapoulkine                      Simon Breuss (16-bit PNM)\n    John-Mark Allen\n    Carmelo J Fdez-Aguera\n\n Bug & warning fixes\n    Marc LeBlanc            David Woo          Guillaume George     Martins Mozeiko\n    Christpher Lloyd        Jerry Jansson      Joseph Thomson       Blazej Dariusz Roszkowski\n    Phil Jordan                                Dave Moore           Roy Eltham\n    Hayaki Saito            Nathan Reed        Won Chun\n    Luke Graham             Johan Duparc       Nick Verigakis       the Horde3D community\n    Thomas Ruf              Ronny Chevalier                         github:rlyeh\n    Janez Zemva             John Bartholomew   Michal Cichon        github:romigrou\n    Jonathan Blow           Ken Hamada         Tero Hanninen        github:svdijk\n    Eugene Golushkov        Laurent Gomila     Cort Stratton        github:snagar\n    Aruelien Pocheville     Sergio Gonzalez    Thibault Reuille     github:Zelex\n    Cass Everitt            Ryamond Barbiero                        github:grim210\n    Paul Du Bois            Engin Manap        Aldo Culquicondor    github:sammyhw\n    Philipp Wiesemann       Dale Weiler        Oriol Ferrer Mesia   github:phprus\n    Josh Tobin              Neil Bickford      Matthew Gregan       github:poppolopoppo\n    Julian Raschke          Gregory Mullen     Christian Floisand   github:darealshinji\n    Baldur Karlsson         Kevin Schmidt      JR Smith             github:Michaelangel007\n                            Brad Weinberger    Matvey Cherevko      github:mosra\n    Luca Sas                Alexander Veselov  Zack Middleton       [reserved]\n    Ryan C. Gordon          [reserved]                              [reserved]\n                     DO NOT ADD YOUR NAME HERE\n\n                     Jacko Dirks\n\n  To add your name to the credits, pick a random blank space in the middle and fill it.\n  80% of merge conflicts on stb PRs are due to people adding their name at the end\n  of the credits.\n*/\n\n#ifndef STBI_INCLUDE_STB_IMAGE_H\n#define STBI_INCLUDE_STB_IMAGE_H\n\n// DOCUMENTATION\n//\n// Limitations:\n//    - no 12-bit-per-channel JPEG\n//    - no JPEGs with arithmetic coding\n//    - GIF always returns *comp=4\n//\n// Basic usage (see HDR discussion below for HDR usage):\n//    int x,y,n;\n//    unsigned char *data = stbi_load(filename, &x, &y, &n, 0);\n//    // ... process data if not NULL ...\n//    // ... x = width, y = height, n = # 8-bit components per pixel ...\n//    // ... replace '0' with '1'..'4' to force that many components per pixel\n//    // ... but 'n' will always be the number that it would have been if you said 0\n//    stbi_image_free(data);\n//\n// Standard parameters:\n//    int *x                 -- outputs image width in pixels\n//    int *y                 -- outputs image height in pixels\n//    int *channels_in_file  -- outputs # of image components in image file\n//    int desired_channels   -- if non-zero, # of image components requested in result\n//\n// The return value from an image loader is an 'unsigned char *' which points\n// to the pixel data, or NULL on an allocation failure or if the image is\n// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,\n// with each pixel consisting of N interleaved 8-bit components; the first\n// pixel pointed to is top-left-most in the image. There is no padding between\n// image scanlines or between pixels, regardless of format. The number of\n// components N is 'desired_channels' if desired_channels is non-zero, or\n// *channels_in_file otherwise. If desired_channels is non-zero,\n// *channels_in_file has the number of components that _would_ have been\n// output otherwise. E.g. if you set desired_channels to 4, you will always\n// get RGBA output, but you can check *channels_in_file to see if it's trivially\n// opaque because e.g. there were only 3 channels in the source image.\n//\n// An output image with N components has the following components interleaved\n// in this order in each pixel:\n//\n//     N=#comp     components\n//       1           grey\n//       2           grey, alpha\n//       3           red, green, blue\n//       4           red, green, blue, alpha\n//\n// If image loading fails for any reason, the return value will be NULL,\n// and *x, *y, *channels_in_file will be unchanged. The function\n// stbi_failure_reason() can be queried for an extremely brief, end-user\n// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS\n// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly\n// more user-friendly ones.\n//\n// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.\n//\n// To query the width, height and component count of an image without having to\n// decode the full file, you can use the stbi_info family of functions:\n//\n//   int x,y,n,ok;\n//   ok = stbi_info(filename, &x, &y, &n);\n//   // returns ok=1 and sets x, y, n if image is a supported format,\n//   // 0 otherwise.\n//\n// Note that stb_image pervasively uses ints in its public API for sizes,\n// including sizes of memory buffers. This is now part of the API and thus\n// hard to change without causing breakage. As a result, the various image\n// loaders all have certain limits on image size; these differ somewhat\n// by format but generally boil down to either just under 2GB or just under\n// 1GB. When the decoded image would be larger than this, stb_image decoding\n// will fail.\n//\n// Additionally, stb_image will reject image files that have any of their\n// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS,\n// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit,\n// the only way to have an image with such dimensions load correctly\n// is for it to have a rather extreme aspect ratio. Either way, the\n// assumption here is that such larger images are likely to be malformed\n// or malicious. If you do need to load an image with individual dimensions\n// larger than that, and it still fits in the overall size limit, you can\n// #define STBI_MAX_DIMENSIONS on your own to be something larger.\n//\n// ===========================================================================\n//\n// UNICODE:\n//\n//   If compiling for Windows and you wish to use Unicode filenames, compile\n//   with\n//       #define STBI_WINDOWS_UTF8\n//   and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert\n//   Windows uint16_t filenames to utf8.\n//\n// ===========================================================================\n//\n// Philosophy\n//\n// stb libraries are designed with the following priorities:\n//\n//    1. easy to use\n//    2. easy to maintain\n//    3. good performance\n//\n// Sometimes I let \"good performance\" creep up in priority over \"easy to maintain\",\n// and for best performance I may provide less-easy-to-use APIs that give higher\n// performance, in addition to the easy-to-use ones. Nevertheless, it's important\n// to keep in mind that from the standpoint of you, a client of this library,\n// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.\n//\n// Some secondary priorities arise directly from the first two, some of which\n// provide more explicit reasons why performance can't be emphasized.\n//\n//    - Portable (\"ease of use\")\n//    - Small source code footprint (\"easy to maintain\")\n//    - No dependencies (\"ease of use\")\n//\n// ===========================================================================\n//\n// I/O callbacks\n//\n// I/O callbacks allow you to read from arbitrary sources, like packaged\n// files or some other source. Data read from callbacks are processed\n// through a small internal buffer (currently 128 bytes) to try to reduce\n// overhead.\n//\n// The three functions you must define are \"read\" (reads some bytes of data),\n// \"skip\" (skips some bytes of data), \"eof\" (reports if the stream is at the end).\n//\n// ===========================================================================\n//\n// SIMD support\n//\n// The JPEG decoder will try to automatically use SIMD kernels on x86 when\n// supported by the compiler. For ARM Neon support, you must explicitly\n// request it.\n//\n// (The old do-it-yourself SIMD API is no longer supported in the current\n// code.)\n//\n// On x86, SSE2 will automatically be used when available based on a run-time\n// test; if not, the generic C versions are used as a fall-back. On ARM targets,\n// the typical path is to have separate builds for NEON and non-NEON devices\n// (at least this is true for iOS and Android). Therefore, the NEON support is\n// toggled by a build flag: define STBI_NEON to get NEON loops.\n//\n// If for some reason you do not want to use any of SIMD code, or if\n// you have issues compiling it, you can disable it entirely by\n// defining STBI_NO_SIMD.\n//\n// ===========================================================================\n//\n// HDR image support   (disable by defining STBI_NO_HDR)\n//\n// stb_image supports loading HDR images in general, and currently the Radiance\n// .HDR file format specifically. You can still load any file through the existing\n// interface; if you attempt to load an HDR file, it will be automatically remapped\n// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;\n// both of these constants can be reconfigured through this interface:\n//\n//     stbi_hdr_to_ldr_gamma(2.2f);\n//     stbi_hdr_to_ldr_scale(1.0f);\n//\n// (note, do not use _inverse_ constants; stbi_image will invert them\n// appropriately).\n//\n// Additionally, there is a new, parallel interface for loading files as\n// (linear) floats to preserve the full dynamic range:\n//\n//    float *data = stbi_loadf(filename, &x, &y, &n, 0);\n//\n// If you load LDR images through this interface, those images will\n// be promoted to floating point values, run through the inverse of\n// constants corresponding to the above:\n//\n//     stbi_ldr_to_hdr_scale(1.0f);\n//     stbi_ldr_to_hdr_gamma(2.2f);\n//\n// Finally, given a filename (or an open file or memory block--see header\n// file for details) containing image data, you can query for the \"most\n// appropriate\" interface to use (that is, whether the image is HDR or\n// not), using:\n//\n//     stbi_is_hdr(char *filename);\n//\n// ===========================================================================\n//\n// iPhone PNG support:\n//\n// We optionally support converting iPhone-formatted PNGs (which store\n// premultiplied BGRA) back to RGB, even though they're internally encoded\n// differently. To enable this conversion, call\n// stbi_convert_iphone_png_to_rgb(1).\n//\n// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per\n// pixel to remove any premultiplied alpha *only* if the image file explicitly\n// says there's premultiplied data (currently only happens in iPhone images,\n// and only if iPhone convert-to-rgb processing is on).\n//\n// ===========================================================================\n//\n// ADDITIONAL CONFIGURATION\n//\n//  - You can suppress implementation of any of the decoders to reduce\n//    your code footprint by #defining one or more of the following\n//    symbols before creating the implementation.\n//\n//        STBI_NO_JPEG\n//        STBI_NO_PNG\n//        STBI_NO_BMP\n//        STBI_NO_PSD\n//        STBI_NO_TGA\n//        STBI_NO_GIF\n//        STBI_NO_HDR\n//        STBI_NO_PIC\n//        STBI_NO_PNM   (.ppm and .pgm)\n//\n//  - You can request *only* certain decoders and suppress all other ones\n//    (this will be more forward-compatible, as addition of new decoders\n//    doesn't require you to disable them explicitly):\n//\n//        STBI_ONLY_JPEG\n//        STBI_ONLY_PNG\n//        STBI_ONLY_BMP\n//        STBI_ONLY_PSD\n//        STBI_ONLY_TGA\n//        STBI_ONLY_GIF\n//        STBI_ONLY_HDR\n//        STBI_ONLY_PIC\n//        STBI_ONLY_PNM   (.ppm and .pgm)\n//\n//   - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still\n//     want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB\n//\n//  - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater\n//    than that size (in either width or height) without further processing.\n//    This is to let programs in the wild set an upper bound to prevent\n//    denial-of-service attacks on untrusted data, as one could generate a\n//    valid image of gigantic dimensions and force stb_image to allocate a\n//    huge block of memory and spend disproportionate time decoding it. By\n//    default this is set to (1 << 24), which is 16777216, but that's still\n//    very big.\n\n#ifndef STBI_NO_STDIO\n#include <stdio.h>\n#endif // STBI_NO_STDIO\n\n#define STBI_VERSION 1\n\nenum\n{\n   STBI_default = 0, // only used for desired_channels\n\n   STBI_grey       = 1,\n   STBI_grey_alpha = 2,\n   STBI_rgb        = 3,\n   STBI_rgb_alpha  = 4\n};\n\n#include <stdlib.h>\ntypedef unsigned char stbi_uc;\ntypedef unsigned short stbi_us;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef STBIDEF\n#ifdef STB_IMAGE_STATIC\n#define STBIDEF static\n#else\n#define STBIDEF extern\n#endif\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// PRIMARY API - works on images of any type\n//\n\n//\n// load image by filename, open file, or memory buffer\n//\n\ntypedef struct\n{\n   int      (*read)  (void *user,char *data,int size);   // fill 'data' with 'size' bytes.  return number of bytes actually read\n   void     (*skip)  (void *user,int n);                 // skip the next 'n' bytes, or 'unget' the last -n bytes if negative\n   int      (*eof)   (void *user);                       // returns nonzero if we are at end of file/data\n} stbi_io_callbacks;\n\n////////////////////////////////////\n//\n// 8-bits-per-channel interface\n//\n\nSTBIDEF stbi_uc *stbi_load_from_memory   (stbi_uc           const *buffer, int len   , int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk  , void *user, int *x, int *y, int *channels_in_file, int desired_channels);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF stbi_uc *stbi_load            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_uc *stbi_load_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n// for stbi_load_from_file, file pointer is left pointing immediately after image\n#endif\n\n#ifndef STBI_NO_GIF\nSTBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);\n#endif\n\n#ifdef STBI_WINDOWS_UTF8\nSTBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const uint16_t* input);\n#endif\n\n////////////////////////////////////\n//\n// 16-bits-per-channel interface\n//\n\nSTBIDEF stbi_us *stbi_load_16_from_memory   (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF stbi_us *stbi_load_16          (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\nSTBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n#endif\n\n////////////////////////////////////\n//\n// float-per-channel interface\n//\n#ifndef STBI_NO_LINEAR\n   STBIDEF float *stbi_loadf_from_memory     (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);\n   STBIDEF float *stbi_loadf_from_callbacks  (stbi_io_callbacks const *clbk, void *user, int *x, int *y,  int *channels_in_file, int desired_channels);\n\n   #ifndef STBI_NO_STDIO\n   STBIDEF float *stbi_loadf            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);\n   STBIDEF float *stbi_loadf_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);\n   #endif\n#endif\n\n#ifndef STBI_NO_HDR\n   STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma);\n   STBIDEF void   stbi_hdr_to_ldr_scale(float scale);\n#endif // STBI_NO_HDR\n\n#ifndef STBI_NO_LINEAR\n   STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma);\n   STBIDEF void   stbi_ldr_to_hdr_scale(float scale);\n#endif // STBI_NO_LINEAR\n\n// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR\nSTBIDEF int    stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);\nSTBIDEF int    stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_is_hdr          (char const *filename);\nSTBIDEF int      stbi_is_hdr_from_file(FILE *f);\n#endif // STBI_NO_STDIO\n\n\n// get a VERY brief reason for failure\n// on most compilers (and ALL modern mainstream compilers) this is threadsafe\nSTBIDEF const char *stbi_failure_reason  (void);\n\n// free the loaded image -- this is just free()\nSTBIDEF void     stbi_image_free      (void *retval_from_stbi_load);\n\n// get image dimensions & components without fully decoding\nSTBIDEF int      stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);\nSTBIDEF int      stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);\nSTBIDEF int      stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);\nSTBIDEF int      stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_info               (char const *filename,     int *x, int *y, int *comp);\nSTBIDEF int      stbi_info_from_file     (FILE *f,                  int *x, int *y, int *comp);\nSTBIDEF int      stbi_is_16_bit          (char const *filename);\nSTBIDEF int      stbi_is_16_bit_from_file(FILE *f);\n#endif\n\n\n\n// for image formats that explicitly notate that they have premultiplied alpha,\n// we just return the colors as stored in the file. set this flag to force\n// unpremultiplication. results are undefined if the unpremultiply overflow.\nSTBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);\n\n// indicate whether we should process iphone images back to canonical format,\n// or just pass them through \"as-is\"\nSTBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);\n\n// flip the image vertically, so the first pixel in the output array is the bottom left\nSTBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);\n\n// as above, but only applies to images loaded on the thread that calls the function\n// this function is only available if your compiler supports thread-local variables;\n// calling it will fail to link if your compiler doesn't\nSTBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);\nSTBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);\nSTBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);\n\n// ZLIB client - used by PNG, available for other purposes\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);\nSTBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);\nSTBIDEF int   stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);\n\nSTBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);\nSTBIDEF int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);\n\n\n#ifdef __cplusplus\n}\n#endif\n\n//\n//\n////   end header file   /////////////////////////////////////////////////////\n#endif // STBI_INCLUDE_STB_IMAGE_H\n\n#ifdef STB_IMAGE_IMPLEMENTATION\n\n#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \\\n  || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \\\n  || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \\\n  || defined(STBI_ONLY_ZLIB)\n   #ifndef STBI_ONLY_JPEG\n   #define STBI_NO_JPEG\n   #endif\n   #ifndef STBI_ONLY_PNG\n   #define STBI_NO_PNG\n   #endif\n   #ifndef STBI_ONLY_BMP\n   #define STBI_NO_BMP\n   #endif\n   #ifndef STBI_ONLY_PSD\n   #define STBI_NO_PSD\n   #endif\n   #ifndef STBI_ONLY_TGA\n   #define STBI_NO_TGA\n   #endif\n   #ifndef STBI_ONLY_GIF\n   #define STBI_NO_GIF\n   #endif\n   #ifndef STBI_ONLY_HDR\n   #define STBI_NO_HDR\n   #endif\n   #ifndef STBI_ONLY_PIC\n   #define STBI_NO_PIC\n   #endif\n   #ifndef STBI_ONLY_PNM\n   #define STBI_NO_PNM\n   #endif\n#endif\n\n#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)\n#define STBI_NO_ZLIB\n#endif\n\n\n#include <stdarg.h>\n#include <stddef.h> // ptrdiff_t on osx\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)\n#include <math.h>  // ldexp, pow\n#endif\n\n#ifndef STBI_NO_STDIO\n#include <stdio.h>\n#endif\n\n#ifndef STBI_ASSERT\n#include <assert.h>\n#define STBI_ASSERT(x) assert(x)\n#endif\n\n#ifdef __cplusplus\n#define STBI_EXTERN extern \"C\"\n#else\n#define STBI_EXTERN extern\n#endif\n\n\n#ifndef _MSC_VER\n   #ifdef __cplusplus\n   #define stbi_inline inline\n   #else\n   #define stbi_inline\n   #endif\n#else\n   #define stbi_inline __forceinline\n#endif\n\n#ifndef STBI_NO_THREAD_LOCALS\n   #if defined(__cplusplus) &&  __cplusplus >= 201103L\n      #define STBI_THREAD_LOCAL       thread_local\n   #elif defined(__GNUC__) && __GNUC__ < 5\n      #define STBI_THREAD_LOCAL       __thread\n   #elif defined(_MSC_VER)\n      #define STBI_THREAD_LOCAL       __declspec(thread)\n   #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)\n      #define STBI_THREAD_LOCAL       _Thread_local\n   #endif\n\n   #ifndef STBI_THREAD_LOCAL\n      #if defined(__GNUC__)\n        #define STBI_THREAD_LOCAL       __thread\n      #endif\n   #endif\n#endif\n\n#if defined(_MSC_VER) || defined(__SYMBIAN32__)\ntypedef unsigned short stbi__uint16;\ntypedef   signed short stbi__int16;\ntypedef unsigned int   stbi__uint32;\ntypedef   signed int   stbi__int32;\n#else\n#include <stdint.h>\ntypedef uint16_t stbi__uint16;\ntypedef int16_t  stbi__int16;\ntypedef uint32_t stbi__uint32;\ntypedef int32_t  stbi__int32;\n#endif\n\n// should produce compiler error if size is wrong\ntypedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];\n\n#ifdef _MSC_VER\n#define STBI_NOTUSED(v)  (void)(v)\n#else\n#define STBI_NOTUSED(v)  (void)sizeof(v)\n#endif\n\n#ifdef _MSC_VER\n#define STBI_HAS_LROTL\n#endif\n\n#ifdef STBI_HAS_LROTL\n   #define stbi_lrot(x,y)  _lrotl(x,y)\n#else\n   #define stbi_lrot(x,y)  (((x) << (y)) | ((x) >> (-(y) & 31)))\n#endif\n\n#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))\n// ok\n#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)\n// ok\n#else\n#error \"Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED).\"\n#endif\n\n#ifndef STBI_MALLOC\n#define STBI_MALLOC(sz)           malloc(sz)\n#define STBI_REALLOC(p,newsz)     realloc(p,newsz)\n#define STBI_FREE(p)              free(p)\n#endif\n\n#ifndef STBI_REALLOC_SIZED\n#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)\n#endif\n\n// x86/x64 detection\n#if defined(__x86_64__) || defined(_M_X64)\n#define STBI__X64_TARGET\n#elif defined(__i386) || defined(_M_IX86)\n#define STBI__X86_TARGET\n#endif\n\n#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)\n// gcc doesn't support sse2 intrinsics unless you compile with -msse2,\n// which in turn means it gets to use SSE2 everywhere. This is unfortunate,\n// but previous attempts to provide the SSE2 functions with runtime\n// detection caused numerous issues. The way architecture extensions are\n// exposed in GCC/Clang is, sadly, not really suited for one-file libs.\n// New behavior: if compiled with -msse2, we use SSE2 without any\n// detection; if not, we don't use it at all.\n#define STBI_NO_SIMD\n#endif\n\n#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)\n// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET\n//\n// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the\n// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.\n// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not\n// simultaneously enabling \"-mstackrealign\".\n//\n// See https://github.com/nothings/stb/issues/81 for more information.\n//\n// So default to no SSE2 on 32-bit MinGW. If you've read this far and added\n// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.\n#define STBI_NO_SIMD\n#endif\n\n#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))\n#define STBI_SSE2\n#include <emmintrin.h>\n\n#ifdef _MSC_VER\n\n#if _MSC_VER >= 1400  // not VC6\n#include <intrin.h> // __cpuid\nstatic int stbi__cpuid3(void)\n{\n   int info[4];\n   __cpuid(info,1);\n   return info[3];\n}\n#else\nstatic int stbi__cpuid3(void)\n{\n   int res;\n   __asm {\n      mov  eax,1\n      cpuid\n      mov  res,edx\n   }\n   return res;\n}\n#endif\n\n#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name\n\n#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)\nstatic int stbi__sse2_available(void)\n{\n   int info3 = stbi__cpuid3();\n   return ((info3 >> 26) & 1) != 0;\n}\n#endif\n\n#else // assume GCC-style if not VC++\n#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))\n\n#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)\nstatic int stbi__sse2_available(void)\n{\n   // If we're even attempting to compile this on GCC/Clang, that means\n   // -msse2 is on, which means the compiler is allowed to use SSE2\n   // instructions at will, and so are we.\n   return 1;\n}\n#endif\n\n#endif\n#endif\n\n// ARM NEON\n#if defined(STBI_NO_SIMD) && defined(STBI_NEON)\n#undef STBI_NEON\n#endif\n\n#ifdef STBI_NEON\n#include <arm_neon.h>\n#ifdef _MSC_VER\n#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name\n#else\n#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))\n#endif\n#endif\n\n#ifndef STBI_SIMD_ALIGN\n#define STBI_SIMD_ALIGN(type, name) type name\n#endif\n\n#ifndef STBI_MAX_DIMENSIONS\n#define STBI_MAX_DIMENSIONS (1 << 24)\n#endif\n\n///////////////////////////////////////////////\n//\n//  stbi__context struct and start_xxx functions\n\n// stbi__context structure is our basic context used by all images, so it\n// contains all the IO context, plus some basic image information\ntypedef struct\n{\n   stbi__uint32 img_x, img_y;\n   int img_n, img_out_n;\n\n   stbi_io_callbacks io;\n   void *io_user_data;\n\n   int read_from_callbacks;\n   int buflen;\n   stbi_uc buffer_start[128];\n   int callback_already_read;\n\n   stbi_uc *img_buffer, *img_buffer_end;\n   stbi_uc *img_buffer_original, *img_buffer_original_end;\n} stbi__context;\n\n\nstatic void stbi__refill_buffer(stbi__context *s);\n\n// initialize a memory-decode context\nstatic void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)\n{\n   s->io.read = NULL;\n   s->read_from_callbacks = 0;\n   s->callback_already_read = 0;\n   s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;\n   s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;\n}\n\n// initialize a callback-based context\nstatic void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)\n{\n   s->io = *c;\n   s->io_user_data = user;\n   s->buflen = sizeof(s->buffer_start);\n   s->read_from_callbacks = 1;\n   s->callback_already_read = 0;\n   s->img_buffer = s->img_buffer_original = s->buffer_start;\n   stbi__refill_buffer(s);\n   s->img_buffer_original_end = s->img_buffer_end;\n}\n\n#ifndef STBI_NO_STDIO\n\nstatic int stbi__stdio_read(void *user, char *data, int size)\n{\n   return (int) fread(data,1,size,(FILE*) user);\n}\n\nstatic void stbi__stdio_skip(void *user, int n)\n{\n   int ch;\n   fseek((FILE*) user, n, SEEK_CUR);\n   ch = fgetc((FILE*) user);  /* have to read a byte to reset feof()'s flag */\n   if (ch != EOF) {\n      ungetc(ch, (FILE *) user);  /* push byte back onto stream if valid. */\n   }\n}\n\nstatic int stbi__stdio_eof(void *user)\n{\n   return feof((FILE*) user) || ferror((FILE *) user);\n}\n\nstatic stbi_io_callbacks stbi__stdio_callbacks =\n{\n   stbi__stdio_read,\n   stbi__stdio_skip,\n   stbi__stdio_eof,\n};\n\nstatic void stbi__start_file(stbi__context *s, FILE *f)\n{\n   stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);\n}\n\n//static void stop_file(stbi__context *s) { }\n\n#endif // !STBI_NO_STDIO\n\nstatic void stbi__rewind(stbi__context *s)\n{\n   // conceptually rewind SHOULD rewind to the beginning of the stream,\n   // but we just rewind to the beginning of the initial buffer, because\n   // we only use it after doing 'test', which only ever looks at at most 92 bytes\n   s->img_buffer = s->img_buffer_original;\n   s->img_buffer_end = s->img_buffer_original_end;\n}\n\nenum\n{\n   STBI_ORDER_RGB,\n   STBI_ORDER_BGR\n};\n\ntypedef struct\n{\n   int bits_per_channel;\n   int num_channels;\n   int channel_order;\n} stbi__result_info;\n\n#ifndef STBI_NO_JPEG\nstatic int      stbi__jpeg_test(stbi__context *s);\nstatic void    *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PNG\nstatic int      stbi__png_test(stbi__context *s);\nstatic void    *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__png_info(stbi__context *s, int *x, int *y, int *comp);\nstatic int      stbi__png_is16(stbi__context *s);\n#endif\n\n#ifndef STBI_NO_BMP\nstatic int      stbi__bmp_test(stbi__context *s);\nstatic void    *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_TGA\nstatic int      stbi__tga_test(stbi__context *s);\nstatic void    *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PSD\nstatic int      stbi__psd_test(stbi__context *s);\nstatic void    *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);\nstatic int      stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);\nstatic int      stbi__psd_is16(stbi__context *s);\n#endif\n\n#ifndef STBI_NO_HDR\nstatic int      stbi__hdr_test(stbi__context *s);\nstatic float   *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PIC\nstatic int      stbi__pic_test(stbi__context *s);\nstatic void    *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_GIF\nstatic int      stbi__gif_test(stbi__context *s);\nstatic void    *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic void    *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);\nstatic int      stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);\n#endif\n\n#ifndef STBI_NO_PNM\nstatic int      stbi__pnm_test(stbi__context *s);\nstatic void    *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);\nstatic int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);\nstatic int      stbi__pnm_is16(stbi__context *s);\n#endif\n\nstatic\n#ifdef STBI_THREAD_LOCAL\nSTBI_THREAD_LOCAL\n#endif\nconst char *stbi__g_failure_reason;\n\nSTBIDEF const char *stbi_failure_reason(void)\n{\n   return stbi__g_failure_reason;\n}\n\n#ifndef STBI_NO_FAILURE_STRINGS\nstatic int stbi__err(const char *str)\n{\n   stbi__g_failure_reason = str;\n   return 0;\n}\n#endif\n\nstatic void *stbi__malloc(size_t size)\n{\n    return STBI_MALLOC(size);\n}\n\n// stb_image uses ints pervasively, including for offset calculations.\n// therefore the largest decoded image size we can support with the\n// current code, even on 64-bit targets, is INT_MAX. this is not a\n// significant limitation for the intended use case.\n//\n// we do, however, need to make sure our size calculations don't\n// overflow. hence a few helper functions for size calculations that\n// multiply integers together, making sure that they're non-negative\n// and no overflow occurs.\n\n// return 1 if the sum is valid, 0 on overflow.\n// negative terms are considered invalid.\nstatic int stbi__addsizes_valid(int a, int b)\n{\n   if (b < 0) return 0;\n   // now 0 <= b <= INT_MAX, hence also\n   // 0 <= INT_MAX - b <= INTMAX.\n   // And \"a + b <= INT_MAX\" (which might overflow) is the\n   // same as a <= INT_MAX - b (no overflow)\n   return a <= INT_MAX - b;\n}\n\n// returns 1 if the product is valid, 0 on overflow.\n// negative factors are considered invalid.\nstatic int stbi__mul2sizes_valid(int a, int b)\n{\n   if (a < 0 || b < 0) return 0;\n   if (b == 0) return 1; // mul-by-0 is always safe\n   // portable way to check for no overflows in a*b\n   return a <= INT_MAX/b;\n}\n\n#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)\n// returns 1 if \"a*b + add\" has no negative terms/factors and doesn't overflow\nstatic int stbi__mad2sizes_valid(int a, int b, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);\n}\n#endif\n\n// returns 1 if \"a*b*c + add\" has no negative terms/factors and doesn't overflow\nstatic int stbi__mad3sizes_valid(int a, int b, int c, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&\n      stbi__addsizes_valid(a*b*c, add);\n}\n\n// returns 1 if \"a*b*c*d + add\" has no negative terms/factors and doesn't overflow\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)\nstatic int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)\n{\n   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&\n      stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);\n}\n#endif\n\n#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)\n// mallocs with size overflow checking\nstatic void *stbi__malloc_mad2(int a, int b, int add)\n{\n   if (!stbi__mad2sizes_valid(a, b, add)) return NULL;\n   return stbi__malloc(a*b + add);\n}\n#endif\n\nstatic void *stbi__malloc_mad3(int a, int b, int c, int add)\n{\n   if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;\n   return stbi__malloc(a*b*c + add);\n}\n\n#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)\nstatic void *stbi__malloc_mad4(int a, int b, int c, int d, int add)\n{\n   if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;\n   return stbi__malloc(a*b*c*d + add);\n}\n#endif\n\n// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.\nstatic int stbi__addints_valid(int a, int b)\n{\n   if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow\n   if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.\n   return a <= INT_MAX - b;\n}\n\n// returns 1 if the product of two ints fits in a signed short, 0 on overflow.\nstatic int stbi__mul2shorts_valid(int a, int b)\n{\n   if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow\n   if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid\n   if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN\n   return a >= SHRT_MIN / b;\n}\n\n// stbi__err - error\n// stbi__errpf - error returning pointer to float\n// stbi__errpuc - error returning pointer to unsigned char\n\n#ifdef STBI_NO_FAILURE_STRINGS\n   #define stbi__err(x,y)  0\n#elif defined(STBI_FAILURE_USERMSG)\n   #define stbi__err(x,y)  stbi__err(y)\n#else\n   #define stbi__err(x,y)  stbi__err(x)\n#endif\n\n#define stbi__errpf(x,y)   ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))\n#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))\n\nSTBIDEF void stbi_image_free(void *retval_from_stbi_load)\n{\n   STBI_FREE(retval_from_stbi_load);\n}\n\n#ifndef STBI_NO_LINEAR\nstatic float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);\n#endif\n\n#ifndef STBI_NO_HDR\nstatic stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);\n#endif\n\nstatic int stbi__vertically_flip_on_load_global = 0;\n\nSTBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)\n{\n   stbi__vertically_flip_on_load_global = flag_true_if_should_flip;\n}\n\n#ifndef STBI_THREAD_LOCAL\n#define stbi__vertically_flip_on_load  stbi__vertically_flip_on_load_global\n#else\nstatic STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;\n\nSTBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)\n{\n   stbi__vertically_flip_on_load_local = flag_true_if_should_flip;\n   stbi__vertically_flip_on_load_set = 1;\n}\n\n#define stbi__vertically_flip_on_load  (stbi__vertically_flip_on_load_set       \\\n                                         ? stbi__vertically_flip_on_load_local  \\\n                                         : stbi__vertically_flip_on_load_global)\n#endif // STBI_THREAD_LOCAL\n\nstatic void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)\n{\n   memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields\n   ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed\n   ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order\n   ri->num_channels = 0;\n\n   // test the formats with a very explicit header first (at least a FOURCC\n   // or distinctive magic number first)\n   #ifndef STBI_NO_PNG\n   if (stbi__png_test(s))  return stbi__png_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_BMP\n   if (stbi__bmp_test(s))  return stbi__bmp_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_GIF\n   if (stbi__gif_test(s))  return stbi__gif_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_test(s))  return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);\n   #else\n   STBI_NOTUSED(bpc);\n   #endif\n   #ifndef STBI_NO_PIC\n   if (stbi__pic_test(s))  return stbi__pic_load(s,x,y,comp,req_comp, ri);\n   #endif\n\n   // then the formats that can end up attempting to load with just 1 or 2\n   // bytes matching expectations; these are prone to false positives, so\n   // try them later\n   #ifndef STBI_NO_JPEG\n   if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);\n   #endif\n   #ifndef STBI_NO_PNM\n   if (stbi__pnm_test(s))  return stbi__pnm_load(s,x,y,comp,req_comp, ri);\n   #endif\n\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_test(s)) {\n      float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);\n      return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);\n   }\n   #endif\n\n   #ifndef STBI_NO_TGA\n   // test tga last because it's a crappy test!\n   if (stbi__tga_test(s))\n      return stbi__tga_load(s,x,y,comp,req_comp, ri);\n   #endif\n\n   return stbi__errpuc(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nstatic stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)\n{\n   int i;\n   int img_len = w * h * channels;\n   stbi_uc *reduced;\n\n   reduced = (stbi_uc *) stbi__malloc(img_len);\n   if (reduced == NULL) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   for (i = 0; i < img_len; ++i)\n      reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling\n\n   STBI_FREE(orig);\n   return reduced;\n}\n\nstatic stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)\n{\n   int i;\n   int img_len = w * h * channels;\n   stbi__uint16 *enlarged;\n\n   enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);\n   if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   for (i = 0; i < img_len; ++i)\n      enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff\n\n   STBI_FREE(orig);\n   return enlarged;\n}\n\nstatic void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)\n{\n   int row;\n   size_t bytes_per_row = (size_t)w * bytes_per_pixel;\n   stbi_uc temp[2048];\n   stbi_uc *bytes = (stbi_uc *)image;\n\n   for (row = 0; row < (h>>1); row++) {\n      stbi_uc *row0 = bytes + row*bytes_per_row;\n      stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;\n      // swap row0 with row1\n      size_t bytes_left = bytes_per_row;\n      while (bytes_left) {\n         size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);\n         memcpy(temp, row0, bytes_copy);\n         memcpy(row0, row1, bytes_copy);\n         memcpy(row1, temp, bytes_copy);\n         row0 += bytes_copy;\n         row1 += bytes_copy;\n         bytes_left -= bytes_copy;\n      }\n   }\n}\n\n#ifndef STBI_NO_GIF\nstatic void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)\n{\n   int slice;\n   int slice_size = w * h * bytes_per_pixel;\n\n   stbi_uc *bytes = (stbi_uc *)image;\n   for (slice = 0; slice < z; ++slice) {\n      stbi__vertical_flip(bytes, w, h, bytes_per_pixel);\n      bytes += slice_size;\n   }\n}\n#endif\n\nstatic unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__result_info ri;\n   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);\n\n   if (result == NULL)\n      return NULL;\n\n   // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.\n   STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);\n\n   if (ri.bits_per_channel != 8) {\n      result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);\n      ri.bits_per_channel = 8;\n   }\n\n   // @TODO: move stbi__convert_format to here\n\n   if (stbi__vertically_flip_on_load) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));\n   }\n\n   return (unsigned char *) result;\n}\n\nstatic stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__result_info ri;\n   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);\n\n   if (result == NULL)\n      return NULL;\n\n   // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.\n   STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);\n\n   if (ri.bits_per_channel != 16) {\n      result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);\n      ri.bits_per_channel = 16;\n   }\n\n   // @TODO: move stbi__convert_format16 to here\n   // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision\n\n   if (stbi__vertically_flip_on_load) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));\n   }\n\n   return (stbi__uint16 *) result;\n}\n\n#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)\nstatic void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)\n{\n   if (stbi__vertically_flip_on_load && result != NULL) {\n      int channels = req_comp ? req_comp : *comp;\n      stbi__vertical_flip(result, *x, *y, channels * sizeof(float));\n   }\n}\n#endif\n\n#ifndef STBI_NO_STDIO\n\n#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)\nSTBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, uint16_t *widestr, int cchwide);\nSTBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const uint16_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);\n#endif\n\n#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)\nSTBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const uint16_t* input)\n{\n\treturn WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);\n}\n#endif\n\nstatic FILE *stbi__fopen(char const *filename, char const *mode)\n{\n   FILE *f;\n#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)\n   uint16_t wMode[64];\n   uint16_t wFilename[1024];\n\tif (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))\n      return 0;\n\n\tif (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))\n      return 0;\n\n#if defined(_MSC_VER) && _MSC_VER >= 1400\n\tif (0 != _wfopen_s(&f, wFilename, wMode))\n\t\tf = 0;\n#else\n   f = _wfopen(wFilename, wMode);\n#endif\n\n#elif defined(_MSC_VER) && _MSC_VER >= 1400\n   if (0 != fopen_s(&f, filename, mode))\n      f=0;\n#else\n   f = fopen(filename, mode);\n#endif\n   return f;\n}\n\n\nSTBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   unsigned char *result;\n   if (!f) return stbi__errpuc(\"can't fopen\", \"Unable to open file\");\n   result = stbi_load_from_file(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\nSTBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   unsigned char *result;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n   if (result) {\n      // need to 'unget' all the characters in the IO buffer\n      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);\n   }\n   return result;\n}\n\nSTBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__uint16 *result;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);\n   if (result) {\n      // need to 'unget' all the characters in the IO buffer\n      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);\n   }\n   return result;\n}\n\nSTBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   stbi__uint16 *result;\n   if (!f) return (stbi_us *) stbi__errpuc(\"can't fopen\", \"Unable to open file\");\n   result = stbi_load_from_file_16(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\n\n#endif //!STBI_NO_STDIO\n\nSTBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);\n}\n\nSTBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);\n   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);\n}\n\nSTBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n}\n\nSTBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);\n}\n\n#ifndef STBI_NO_GIF\nSTBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)\n{\n   unsigned char *result;\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n\n   result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);\n   if (stbi__vertically_flip_on_load) {\n      stbi__vertical_flip_slices( result, *x, *y, *z, *comp );\n   }\n\n   return result;\n}\n#endif\n\n#ifndef STBI_NO_LINEAR\nstatic float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)\n{\n   unsigned char *data;\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_test(s)) {\n      stbi__result_info ri;\n      float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);\n      if (hdr_data)\n         stbi__float_postprocess(hdr_data,x,y,comp,req_comp);\n      return hdr_data;\n   }\n   #endif\n   data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);\n   if (data)\n      return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);\n   return stbi__errpf(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nSTBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n\nSTBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)\n{\n   float *result;\n   FILE *f = stbi__fopen(filename, \"rb\");\n   if (!f) return stbi__errpf(\"can't fopen\", \"Unable to open file\");\n   result = stbi_loadf_from_file(f,x,y,comp,req_comp);\n   fclose(f);\n   return result;\n}\n\nSTBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)\n{\n   stbi__context s;\n   stbi__start_file(&s,f);\n   return stbi__loadf_main(&s,x,y,comp,req_comp);\n}\n#endif // !STBI_NO_STDIO\n\n#endif // !STBI_NO_LINEAR\n\n// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is\n// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always\n// reports false!\n\nSTBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)\n{\n   #ifndef STBI_NO_HDR\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__hdr_test(&s);\n   #else\n   STBI_NOTUSED(buffer);\n   STBI_NOTUSED(len);\n   return 0;\n   #endif\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int      stbi_is_hdr          (char const *filename)\n{\n   FILE *f = stbi__fopen(filename, \"rb\");\n   int result=0;\n   if (f) {\n      result = stbi_is_hdr_from_file(f);\n      fclose(f);\n   }\n   return result;\n}\n\nSTBIDEF int stbi_is_hdr_from_file(FILE *f)\n{\n   #ifndef STBI_NO_HDR\n   long pos = ftell(f);\n   int res;\n   stbi__context s;\n   stbi__start_file(&s,f);\n   res = stbi__hdr_test(&s);\n   fseek(f, pos, SEEK_SET);\n   return res;\n   #else\n   STBI_NOTUSED(f);\n   return 0;\n   #endif\n}\n#endif // !STBI_NO_STDIO\n\nSTBIDEF int      stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)\n{\n   #ifndef STBI_NO_HDR\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);\n   return stbi__hdr_test(&s);\n   #else\n   STBI_NOTUSED(clbk);\n   STBI_NOTUSED(user);\n   return 0;\n   #endif\n}\n\n#ifndef STBI_NO_LINEAR\nstatic float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;\n\nSTBIDEF void   stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }\nSTBIDEF void   stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }\n#endif\n\nstatic float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;\n\nSTBIDEF void   stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }\nSTBIDEF void   stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// Common code used by all image loaders\n//\n\nenum\n{\n   STBI__SCAN_load=0,\n   STBI__SCAN_type,\n   STBI__SCAN_header\n};\n\nstatic void stbi__refill_buffer(stbi__context *s)\n{\n   int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);\n   s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original);\n   if (n == 0) {\n      // at end of file, treat same as if from memory, but need to handle case\n      // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file\n      s->read_from_callbacks = 0;\n      s->img_buffer = s->buffer_start;\n      s->img_buffer_end = s->buffer_start+1;\n      *s->img_buffer = 0;\n   } else {\n      s->img_buffer = s->buffer_start;\n      s->img_buffer_end = s->buffer_start + n;\n   }\n}\n\nstbi_inline static stbi_uc stbi__get8(stbi__context *s)\n{\n   if (s->img_buffer < s->img_buffer_end)\n      return *s->img_buffer++;\n   if (s->read_from_callbacks) {\n      stbi__refill_buffer(s);\n      return *s->img_buffer++;\n   }\n   return 0;\n}\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\nstbi_inline static int stbi__at_eof(stbi__context *s)\n{\n   if (s->io.read) {\n      if (!(s->io.eof)(s->io_user_data)) return 0;\n      // if feof() is true, check if buffer = end\n      // special case: we've only got the special 0 character at the end\n      if (s->read_from_callbacks == 0) return 1;\n   }\n\n   return s->img_buffer >= s->img_buffer_end;\n}\n#endif\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic void stbi__skip(stbi__context *s, int n)\n{\n   if (n == 0) return;  // already there!\n   if (n < 0) {\n      s->img_buffer = s->img_buffer_end;\n      return;\n   }\n   if (s->io.read) {\n      int blen = (int) (s->img_buffer_end - s->img_buffer);\n      if (blen < n) {\n         s->img_buffer = s->img_buffer_end;\n         (s->io.skip)(s->io_user_data, n - blen);\n         return;\n      }\n   }\n   s->img_buffer += n;\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)\n// nothing\n#else\nstatic int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)\n{\n   if (s->io.read) {\n      int blen = (int) (s->img_buffer_end - s->img_buffer);\n      if (blen < n) {\n         int res, count;\n\n         memcpy(buffer, s->img_buffer, blen);\n\n         count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);\n         res = (count == (n-blen));\n         s->img_buffer = s->img_buffer_end;\n         return res;\n      }\n   }\n\n   if (s->img_buffer+n <= s->img_buffer_end) {\n      memcpy(buffer, s->img_buffer, n);\n      s->img_buffer += n;\n      return 1;\n   } else\n      return 0;\n}\n#endif\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic int stbi__get16be(stbi__context *s)\n{\n   int z = stbi__get8(s);\n   return (z << 8) + stbi__get8(s);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)\n// nothing\n#else\nstatic stbi__uint32 stbi__get32be(stbi__context *s)\n{\n   stbi__uint32 z = stbi__get16be(s);\n   return (z << 16) + stbi__get16be(s);\n}\n#endif\n\n#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)\n// nothing\n#else\nstatic int stbi__get16le(stbi__context *s)\n{\n   int z = stbi__get8(s);\n   return z + (stbi__get8(s) << 8);\n}\n#endif\n\n#ifndef STBI_NO_BMP\nstatic stbi__uint32 stbi__get32le(stbi__context *s)\n{\n   stbi__uint32 z = stbi__get16le(s);\n   z += (stbi__uint32)stbi__get16le(s) << 16;\n   return z;\n}\n#endif\n\n#define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings\n\n#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\n//////////////////////////////////////////////////////////////////////////////\n//\n//  generic converter from built-in img_n to req_comp\n//    individual types do this automatically as much as possible (e.g. jpeg\n//    does all cases internally since it needs to colorspace convert anyway,\n//    and it never has alpha, so very few cases ). png can automatically\n//    interleave an alpha=255 channel, but falls back to this for other cases\n//\n//  assume data buffer is malloced, so malloc a new one and free that one\n//  only failure mode is malloc failing\n\nstatic stbi_uc stbi__compute_y(int r, int g, int b)\n{\n   return (stbi_uc) (((r*77) + (g*150) +  (29*b)) >> 8);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)\n// nothing\n#else\nstatic unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)\n{\n   int i,j;\n   unsigned char *good;\n\n   if (req_comp == img_n) return data;\n   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);\n\n   good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);\n   if (good == NULL) {\n      STBI_FREE(data);\n      return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   }\n\n   for (j=0; j < (int) y; ++j) {\n      unsigned char *src  = data + j * x * img_n   ;\n      unsigned char *dest = good + j * x * req_comp;\n\n      #define STBI__COMBO(a,b)  ((a)*8+(b))\n      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)\n      // convert source image with img_n components to one with req_comp components;\n      // avoid switch per pixel, so use switch per scanline and massive macros\n      switch (STBI__COMBO(img_n, req_comp)) {\n         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255;                                     } break;\n         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;\n         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255;                     } break;\n         STBI__CASE(2,1) { dest[0]=src[0];                                                  } break;\n         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;\n         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                  } break;\n         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255;        } break;\n         STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255;    } break;\n         STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;\n         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                    } break;\n         default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc(\"unsupported\", \"Unsupported format conversion\");\n      }\n      #undef STBI__CASE\n   }\n\n   STBI_FREE(data);\n   return good;\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)\n// nothing\n#else\nstatic stbi__uint16 stbi__compute_y_16(int r, int g, int b)\n{\n   return (stbi__uint16) (((r*77) + (g*150) +  (29*b)) >> 8);\n}\n#endif\n\n#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)\n// nothing\n#else\nstatic stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)\n{\n   int i,j;\n   stbi__uint16 *good;\n\n   if (req_comp == img_n) return data;\n   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);\n\n   good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);\n   if (good == NULL) {\n      STBI_FREE(data);\n      return (stbi__uint16 *) stbi__errpuc(\"outofmem\", \"Out of memory\");\n   }\n\n   for (j=0; j < (int) y; ++j) {\n      stbi__uint16 *src  = data + j * x * img_n   ;\n      stbi__uint16 *dest = good + j * x * req_comp;\n\n      #define STBI__COMBO(a,b)  ((a)*8+(b))\n      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)\n      // convert source image with img_n components to one with req_comp components;\n      // avoid switch per pixel, so use switch per scanline and massive macros\n      switch (STBI__COMBO(img_n, req_comp)) {\n         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff;                                     } break;\n         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;\n         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff;                     } break;\n         STBI__CASE(2,1) { dest[0]=src[0];                                                     } break;\n         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;\n         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                     } break;\n         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff;        } break;\n         STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;\n         STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;\n         STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;\n         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                       } break;\n         default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc(\"unsupported\", \"Unsupported format conversion\");\n      }\n      #undef STBI__CASE\n   }\n\n   STBI_FREE(data);\n   return good;\n}\n#endif\n\n#ifndef STBI_NO_LINEAR\nstatic float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)\n{\n   int i,k,n;\n   float *output;\n   if (!data) return NULL;\n   output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);\n   if (output == NULL) { STBI_FREE(data); return stbi__errpf(\"outofmem\", \"Out of memory\"); }\n   // compute number of non-alpha components\n   if (comp & 1) n = comp; else n = comp-1;\n   for (i=0; i < x*y; ++i) {\n      for (k=0; k < n; ++k) {\n         output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);\n      }\n   }\n   if (n < comp) {\n      for (i=0; i < x*y; ++i) {\n         output[i*comp + n] = data[i*comp + n]/255.0f;\n      }\n   }\n   STBI_FREE(data);\n   return output;\n}\n#endif\n\n#ifndef STBI_NO_HDR\n#define stbi__float2int(x)   ((int) (x))\nstatic stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp)\n{\n   int i,k,n;\n   stbi_uc *output;\n   if (!data) return NULL;\n   output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);\n   if (output == NULL) { STBI_FREE(data); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n   // compute number of non-alpha components\n   if (comp & 1) n = comp; else n = comp-1;\n   for (i=0; i < x*y; ++i) {\n      for (k=0; k < n; ++k) {\n         float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;\n         if (z < 0) z = 0;\n         if (z > 255) z = 255;\n         output[i*comp + k] = (stbi_uc) stbi__float2int(z);\n      }\n      if (k < comp) {\n         float z = data[i*comp+k] * 255 + 0.5f;\n         if (z < 0) z = 0;\n         if (z > 255) z = 255;\n         output[i*comp + k] = (stbi_uc) stbi__float2int(z);\n      }\n   }\n   STBI_FREE(data);\n   return output;\n}\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n//  \"baseline\" JPEG/JFIF decoder\n//\n//    simple implementation\n//      - doesn't support delayed output of y-dimension\n//      - simple interface (only one output format: 8-bit interleaved RGB)\n//      - doesn't try to recover corrupt jpegs\n//      - doesn't allow partial loading, loading multiple at once\n//      - still fast on x86 (copying globals into locals doesn't help x86)\n//      - allocates lots of intermediate memory (full size of all components)\n//        - non-interleaved case requires this anyway\n//        - allows good upsampling (see next)\n//    high-quality\n//      - upsampled channels are bilinearly interpolated, even across blocks\n//      - quality integer IDCT derived from IJG's 'slow'\n//    performance\n//      - fast huffman; reasonable integer IDCT\n//      - some SIMD kernels for common paths on targets with SSE2/NEON\n//      - uses a lot of intermediate memory, could cache poorly\n\n#ifndef STBI_NO_JPEG\n\n// huffman decoding acceleration\n#define FAST_BITS   9  // larger handles more cases; smaller stomps less cache\n\ntypedef struct\n{\n   stbi_uc  fast[1 << FAST_BITS];\n   // weirdly, repacking this into AoS is a 10% speed loss, instead of a win\n   stbi__uint16 code[256];\n   stbi_uc  values[256];\n   stbi_uc  size[257];\n   unsigned int maxcode[18];\n   int    delta[17];   // old 'firstsymbol' - old 'firstcode'\n} stbi__huffman;\n\ntypedef struct\n{\n   stbi__context *s;\n   stbi__huffman huff_dc[4];\n   stbi__huffman huff_ac[4];\n   stbi__uint16 dequant[4][64];\n   stbi__int16 fast_ac[4][1 << FAST_BITS];\n\n// sizes for components, interleaved MCUs\n   int img_h_max, img_v_max;\n   int img_mcu_x, img_mcu_y;\n   int img_mcu_w, img_mcu_h;\n\n// definition of jpeg image component\n   struct\n   {\n      int id;\n      int h,v;\n      int tq;\n      int hd,ha;\n      int dc_pred;\n\n      int x,y,w2,h2;\n      stbi_uc *data;\n      void *raw_data, *raw_coeff;\n      stbi_uc *linebuf;\n      short   *coeff;   // progressive only\n      int      coeff_w, coeff_h; // number of 8x8 coefficient blocks\n   } img_comp[4];\n\n   stbi__uint32   code_buffer; // jpeg entropy-coded buffer\n   int            code_bits;   // number of valid bits\n   unsigned char  marker;      // marker seen while filling entropy buffer\n   int            nomore;      // flag if we saw a marker so must stop\n\n   int            progressive;\n   int            spec_start;\n   int            spec_end;\n   int            succ_high;\n   int            succ_low;\n   int            eob_run;\n   int            jfif;\n   int            app14_color_transform; // Adobe APP14 tag\n   int            rgb;\n\n   int scan_n, order[4];\n   int restart_interval, todo;\n\n// kernels\n   void (*idct_block_kernel)(stbi_uc *out, int ort_stride, short data[64]);\n   void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);\n   stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);\n} stbi__jpeg;\n\nstatic int stbi__build_huffman(stbi__huffman *h, int *count)\n{\n   int i,j,k=0;\n   unsigned int code;\n   // build size list for each symbol (from JPEG spec)\n   for (i=0; i < 16; ++i) {\n      for (j=0; j < count[i]; ++j) {\n         h->size[k++] = (stbi_uc) (i+1);\n         if(k >= 257) return stbi__err(\"bad size list\",\"Corrupt JPEG\");\n      }\n   }\n   h->size[k] = 0;\n\n   // compute actual symbols (from jpeg spec)\n   code = 0;\n   k = 0;\n   for(j=1; j <= 16; ++j) {\n      // compute delta to add to code to compute symbol id\n      h->delta[j] = k - code;\n      if (h->size[k] == j) {\n         while (h->size[k] == j)\n            h->code[k++] = (stbi__uint16) (code++);\n         if (code-1 >= (1u << j)) return stbi__err(\"bad code lengths\",\"Corrupt JPEG\");\n      }\n      // compute largest code + 1 for this size, preshifted as needed later\n      h->maxcode[j] = code << (16-j);\n      code <<= 1;\n   }\n   h->maxcode[j] = 0xffffffff;\n\n   // build non-spec acceleration table; 255 is flag for not-accelerated\n   memset(h->fast, 255, 1 << FAST_BITS);\n   for (i=0; i < k; ++i) {\n      int s = h->size[i];\n      if (s <= FAST_BITS) {\n         int c = h->code[i] << (FAST_BITS-s);\n         int m = 1 << (FAST_BITS-s);\n         for (j=0; j < m; ++j) {\n            h->fast[c+j] = (stbi_uc) i;\n         }\n      }\n   }\n   return 1;\n}\n\n// build a table that decodes both magnitude and value of small ACs in\n// one go.\nstatic void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)\n{\n   int i;\n   for (i=0; i < (1 << FAST_BITS); ++i) {\n      stbi_uc fast = h->fast[i];\n      fast_ac[i] = 0;\n      if (fast < 255) {\n         int rs = h->values[fast];\n         int run = (rs >> 4) & 15;\n         int magbits = rs & 15;\n         int len = h->size[fast];\n\n         if (magbits && len + magbits <= FAST_BITS) {\n            // magnitude code followed by receive_extend code\n            int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);\n            int m = 1 << (magbits - 1);\n            if (k < m) k += (~0U << magbits) + 1;\n            // if the result is small enough, we can fit it in fast_ac table\n            if (k >= -128 && k <= 127)\n               fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));\n         }\n      }\n   }\n}\n\nstatic void stbi__grow_buffer_unsafe(stbi__jpeg *j)\n{\n   do {\n      unsigned int b = j->nomore ? 0 : stbi__get8(j->s);\n      if (b == 0xff) {\n         int c = stbi__get8(j->s);\n         while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes\n         if (c != 0) {\n            j->marker = (unsigned char) c;\n            j->nomore = 1;\n            return;\n         }\n      }\n      j->code_buffer |= b << (24 - j->code_bits);\n      j->code_bits += 8;\n   } while (j->code_bits <= 24);\n}\n\n// (1 << n) - 1\nstatic const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};\n\n// decode a jpeg huffman value from the bitstream\nstbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)\n{\n   unsigned int temp;\n   int c,k;\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n\n   // look at the top FAST_BITS and determine what symbol ID it is,\n   // if the code is <= FAST_BITS\n   c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n   k = h->fast[c];\n   if (k < 255) {\n      int s = h->size[k];\n      if (s > j->code_bits)\n         return -1;\n      j->code_buffer <<= s;\n      j->code_bits -= s;\n      return h->values[k];\n   }\n\n   // naive test is to shift the code_buffer down so k bits are\n   // valid, then test against maxcode. To speed this up, we've\n   // preshifted maxcode left so that it has (16-k) 0s at the\n   // end; in other words, regardless of the number of bits, it\n   // wants to be compared against something shifted to have 16;\n   // that way we don't need to shift inside the loop.\n   temp = j->code_buffer >> 16;\n   for (k=FAST_BITS+1 ; ; ++k)\n      if (temp < h->maxcode[k])\n         break;\n   if (k == 17) {\n      // error! code not found\n      j->code_bits -= 16;\n      return -1;\n   }\n\n   if (k > j->code_bits)\n      return -1;\n\n   // convert the huffman code to the symbol id\n   c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];\n   if(c < 0 || c >= 256) // symbol id out of bounds!\n       return -1;\n   STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);\n\n   // convert the id to a symbol\n   j->code_bits -= k;\n   j->code_buffer <<= k;\n   return h->values[c];\n}\n\n// bias[n] = (-1<<n) + 1\nstatic const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};\n\n// combined JPEG 'receive' and JPEG 'extend', since baseline\n// always extends everything it receives.\nstbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)\n{\n   unsigned int k;\n   int sgn;\n   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);\n   if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing\n\n   sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)\n   k = stbi_lrot(j->code_buffer, n);\n   j->code_buffer = k & ~stbi__bmask[n];\n   k &= stbi__bmask[n];\n   j->code_bits -= n;\n   return k + (stbi__jbias[n] & (sgn - 1));\n}\n\n// get some unsigned bits\nstbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)\n{\n   unsigned int k;\n   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);\n   if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing\n   k = stbi_lrot(j->code_buffer, n);\n   j->code_buffer = k & ~stbi__bmask[n];\n   k &= stbi__bmask[n];\n   j->code_bits -= n;\n   return k;\n}\n\nstbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)\n{\n   unsigned int k;\n   if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);\n   if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing\n   k = j->code_buffer;\n   j->code_buffer <<= 1;\n   --j->code_bits;\n   return k & 0x80000000;\n}\n\n// given a value that's at position X in the zigzag stream,\n// where does it appear in the 8x8 matrix coded as row-major?\nstatic const stbi_uc stbi__jpeg_dezigzag[64+15] =\n{\n    0,  1,  8, 16,  9,  2,  3, 10,\n   17, 24, 32, 25, 18, 11,  4,  5,\n   12, 19, 26, 33, 40, 48, 41, 34,\n   27, 20, 13,  6,  7, 14, 21, 28,\n   35, 42, 49, 56, 57, 50, 43, 36,\n   29, 22, 15, 23, 30, 37, 44, 51,\n   58, 59, 52, 45, 38, 31, 39, 46,\n   53, 60, 61, 54, 47, 55, 62, 63,\n   // let corrupt input sample past end\n   63, 63, 63, 63, 63, 63, 63, 63,\n   63, 63, 63, 63, 63, 63, 63\n};\n\n// decode one 64-entry block--\nstatic int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)\n{\n   int diff,dc,k;\n   int t;\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n   t = stbi__jpeg_huff_decode(j, hdc);\n   if (t < 0 || t > 15) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n\n   // 0 all the ac values now so we can do it 32-bits at a time\n   memset(data,0,64*sizeof(data[0]));\n\n   diff = t ? stbi__extend_receive(j, t) : 0;\n   if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err(\"bad delta\",\"Corrupt JPEG\");\n   dc = j->img_comp[b].dc_pred + diff;\n   j->img_comp[b].dc_pred = dc;\n   if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n   data[0] = (short) (dc * dequant[0]);\n\n   // decode AC components, see JPEG spec\n   k = 1;\n   do {\n      unsigned int zig;\n      int c,r,s;\n      if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n      c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n      r = fac[c];\n      if (r) { // fast-AC path\n         k += (r >> 4) & 15; // run\n         s = r & 15; // combined length\n         if (s > j->code_bits) return stbi__err(\"bad huffman code\", \"Combined length longer than code bits available\");\n         j->code_buffer <<= s;\n         j->code_bits -= s;\n         // decode into unzigzag'd location\n         zig = stbi__jpeg_dezigzag[k++];\n         data[zig] = (short) ((r >> 8) * dequant[zig]);\n      } else {\n         int rs = stbi__jpeg_huff_decode(j, hac);\n         if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n         s = rs & 15;\n         r = rs >> 4;\n         if (s == 0) {\n            if (rs != 0xf0) break; // end block\n            k += 16;\n         } else {\n            k += r;\n            // decode into unzigzag'd location\n            zig = stbi__jpeg_dezigzag[k++];\n            data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);\n         }\n      }\n   } while (k < 64);\n   return 1;\n}\n\nstatic int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)\n{\n   int diff,dc;\n   int t;\n   if (j->spec_end != 0) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n\n   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n\n   if (j->succ_high == 0) {\n      // first scan for DC coefficient, must be first\n      memset(data,0,64*sizeof(data[0])); // 0 all the ac values now\n      t = stbi__jpeg_huff_decode(j, hdc);\n      if (t < 0 || t > 15) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n      diff = t ? stbi__extend_receive(j, t) : 0;\n\n      if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err(\"bad delta\", \"Corrupt JPEG\");\n      dc = j->img_comp[b].dc_pred + diff;\n      j->img_comp[b].dc_pred = dc;\n      if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n      data[0] = (short) (dc * (1 << j->succ_low));\n   } else {\n      // refinement scan for DC coefficient\n      if (stbi__jpeg_get_bit(j))\n         data[0] += (short) (1 << j->succ_low);\n   }\n   return 1;\n}\n\n// @OPTIMIZE: store non-zigzagged during the decode passes,\n// and only de-zigzag when dequantizing\nstatic int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)\n{\n   int k;\n   if (j->spec_start == 0) return stbi__err(\"can't merge dc and ac\", \"Corrupt JPEG\");\n\n   if (j->succ_high == 0) {\n      int shift = j->succ_low;\n\n      if (j->eob_run) {\n         --j->eob_run;\n         return 1;\n      }\n\n      k = j->spec_start;\n      do {\n         unsigned int zig;\n         int c,r,s;\n         if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);\n         c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);\n         r = fac[c];\n         if (r) { // fast-AC path\n            k += (r >> 4) & 15; // run\n            s = r & 15; // combined length\n            if (s > j->code_bits) return stbi__err(\"bad huffman code\", \"Combined length longer than code bits available\");\n            j->code_buffer <<= s;\n            j->code_bits -= s;\n            zig = stbi__jpeg_dezigzag[k++];\n            data[zig] = (short) ((r >> 8) * (1 << shift));\n         } else {\n            int rs = stbi__jpeg_huff_decode(j, hac);\n            if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n            s = rs & 15;\n            r = rs >> 4;\n            if (s == 0) {\n               if (r < 15) {\n                  j->eob_run = (1 << r);\n                  if (r)\n                     j->eob_run += stbi__jpeg_get_bits(j, r);\n                  --j->eob_run;\n                  break;\n               }\n               k += 16;\n            } else {\n               k += r;\n               zig = stbi__jpeg_dezigzag[k++];\n               data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift));\n            }\n         }\n      } while (k <= j->spec_end);\n   } else {\n      // refinement scan for these AC coefficients\n\n      short bit = (short) (1 << j->succ_low);\n\n      if (j->eob_run) {\n         --j->eob_run;\n         for (k = j->spec_start; k <= j->spec_end; ++k) {\n            short *p = &data[stbi__jpeg_dezigzag[k]];\n            if (*p != 0)\n               if (stbi__jpeg_get_bit(j))\n                  if ((*p & bit)==0) {\n                     if (*p > 0)\n                        *p += bit;\n                     else\n                        *p -= bit;\n                  }\n         }\n      } else {\n         k = j->spec_start;\n         do {\n            int r,s;\n            int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh\n            if (rs < 0) return stbi__err(\"bad huffman code\",\"Corrupt JPEG\");\n            s = rs & 15;\n            r = rs >> 4;\n            if (s == 0) {\n               if (r < 15) {\n                  j->eob_run = (1 << r) - 1;\n                  if (r)\n                     j->eob_run += stbi__jpeg_get_bits(j, r);\n                  r = 64; // force end of block\n               } else {\n                  // r=15 s=0 should write 16 0s, so we just do\n                  // a run of 15 0s and then write s (which is 0),\n                  // so we don't have to do anything special here\n               }\n            } else {\n               if (s != 1) return stbi__err(\"bad huffman code\", \"Corrupt JPEG\");\n               // sign bit\n               if (stbi__jpeg_get_bit(j))\n                  s = bit;\n               else\n                  s = -bit;\n            }\n\n            // advance by r\n            while (k <= j->spec_end) {\n               short *p = &data[stbi__jpeg_dezigzag[k++]];\n               if (*p != 0) {\n                  if (stbi__jpeg_get_bit(j))\n                     if ((*p & bit)==0) {\n                        if (*p > 0)\n                           *p += bit;\n                        else\n                           *p -= bit;\n                     }\n               } else {\n                  if (r == 0) {\n                     *p = (short) s;\n                     break;\n                  }\n                  --r;\n               }\n            }\n         } while (k <= j->spec_end);\n      }\n   }\n   return 1;\n}\n\n// take a -128..127 value and stbi__clamp it and convert to 0..255\nstbi_inline static stbi_uc stbi__clamp(int x)\n{\n   // trick to use a single test to catch both cases\n   if ((unsigned int) x > 255) {\n      if (x < 0) return 0;\n      if (x > 255) return 255;\n   }\n   return (stbi_uc) x;\n}\n\n#define stbi__f2f(x)  ((int) (((x) * 4096 + 0.5)))\n#define stbi__fsh(x)  ((x) * 4096)\n\n// derived from jidctint -- DCT_ISLOW\n#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \\\n   int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \\\n   p2 = s2;                                    \\\n   p3 = s6;                                    \\\n   p1 = (p2+p3) * stbi__f2f(0.5411961f);       \\\n   t2 = p1 + p3*stbi__f2f(-1.847759065f);      \\\n   t3 = p1 + p2*stbi__f2f( 0.765366865f);      \\\n   p2 = s0;                                    \\\n   p3 = s4;                                    \\\n   t0 = stbi__fsh(p2+p3);                      \\\n   t1 = stbi__fsh(p2-p3);                      \\\n   x0 = t0+t3;                                 \\\n   x3 = t0-t3;                                 \\\n   x1 = t1+t2;                                 \\\n   x2 = t1-t2;                                 \\\n   t0 = s7;                                    \\\n   t1 = s5;                                    \\\n   t2 = s3;                                    \\\n   t3 = s1;                                    \\\n   p3 = t0+t2;                                 \\\n   p4 = t1+t3;                                 \\\n   p1 = t0+t3;                                 \\\n   p2 = t1+t2;                                 \\\n   p5 = (p3+p4)*stbi__f2f( 1.175875602f);      \\\n   t0 = t0*stbi__f2f( 0.298631336f);           \\\n   t1 = t1*stbi__f2f( 2.053119869f);           \\\n   t2 = t2*stbi__f2f( 3.072711026f);           \\\n   t3 = t3*stbi__f2f( 1.501321110f);           \\\n   p1 = p5 + p1*stbi__f2f(-0.899976223f);      \\\n   p2 = p5 + p2*stbi__f2f(-2.562915447f);      \\\n   p3 = p3*stbi__f2f(-1.961570560f);           \\\n   p4 = p4*stbi__f2f(-0.390180644f);           \\\n   t3 += p1+p4;                                \\\n   t2 += p2+p3;                                \\\n   t1 += p2+p4;                                \\\n   t0 += p1+p3;\n\nstatic void stbi__idct_block(stbi_uc *out, int ort_stride, short data[64])\n{\n   int i,val[64],*v=val;\n   stbi_uc *o;\n   short *d = data;\n\n   // columns\n   for (i=0; i < 8; ++i,++d, ++v) {\n      // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing\n      if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0\n           && d[40]==0 && d[48]==0 && d[56]==0) {\n         //    no shortcut                 0     seconds\n         //    (1|2|3|4|5|6|7)==0          0     seconds\n         //    all separate               -0.047 seconds\n         //    1 && 2|3 && 4|5 && 6|7:    -0.047 seconds\n         int dcterm = d[0]*4;\n         v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;\n      } else {\n         STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])\n         // constants scaled things up by 1<<12; let's bring them back\n         // down, but keep 2 extra bits of precision\n         x0 += 512; x1 += 512; x2 += 512; x3 += 512;\n         v[ 0] = (x0+t3) >> 10;\n         v[56] = (x0-t3) >> 10;\n         v[ 8] = (x1+t2) >> 10;\n         v[48] = (x1-t2) >> 10;\n         v[16] = (x2+t1) >> 10;\n         v[40] = (x2-t1) >> 10;\n         v[24] = (x3+t0) >> 10;\n         v[32] = (x3-t0) >> 10;\n      }\n   }\n\n   for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=ort_stride) {\n      // no fast case since the first 1D IDCT spread components out\n      STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])\n      // constants scaled things up by 1<<12, plus we had 1<<2 from first\n      // loop, plus horizontal and vertical each scale by sqrt(8) so together\n      // we've got an extra 1<<3, so 1<<17 total we need to remove.\n      // so we want to round that, which means adding 0.5 * 1<<17,\n      // aka 65536. Also, we'll end up with -128 to 127 that we want\n      // to encode as 0..255 by adding 128, so we'll add that before the shift\n      x0 += 65536 + (128<<17);\n      x1 += 65536 + (128<<17);\n      x2 += 65536 + (128<<17);\n      x3 += 65536 + (128<<17);\n      // tried computing the shifts into temps, or'ing the temps to see\n      // if any were out of range, but that was slower\n      o[0] = stbi__clamp((x0+t3) >> 17);\n      o[7] = stbi__clamp((x0-t3) >> 17);\n      o[1] = stbi__clamp((x1+t2) >> 17);\n      o[6] = stbi__clamp((x1-t2) >> 17);\n      o[2] = stbi__clamp((x2+t1) >> 17);\n      o[5] = stbi__clamp((x2-t1) >> 17);\n      o[3] = stbi__clamp((x3+t0) >> 17);\n      o[4] = stbi__clamp((x3-t0) >> 17);\n   }\n}\n\n#ifdef STBI_SSE2\n// sse2 integer IDCT. not the fastest possible implementation but it\n// produces bit-identical results to the generic C version so it's\n// fully \"transparent\".\nstatic void stbi__idct_simd(stbi_uc *out, int ort_stride, short data[64])\n{\n   // This is constructed to match our regular (generic) integer IDCT exactly.\n   __m128i row0, row1, row2, row3, row4, row5, row6, row7;\n   __m128i tmp;\n\n   // dot product constant: even elems=x, odd elems=y\n   #define dct_const(x,y)  _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))\n\n   // out(0) = c0[even]*x + c0[odd]*y   (c0, x, y 16-bit, out 32-bit)\n   // out(1) = c1[even]*x + c1[odd]*y\n   #define dct_rot(out0,out1, x,y,c0,c1) \\\n      __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \\\n      __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \\\n      __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \\\n      __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \\\n      __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \\\n      __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)\n\n   // out = in << 12  (in 16-bit, out 32-bit)\n   #define dct_widen(out, in) \\\n      __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \\\n      __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)\n\n   // wide add\n   #define dct_wadd(out, a, b) \\\n      __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \\\n      __m128i out##_h = _mm_add_epi32(a##_h, b##_h)\n\n   // wide sub\n   #define dct_wsub(out, a, b) \\\n      __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \\\n      __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)\n\n   // butterfly a/b, add bias, then shift by \"s\" and pack\n   #define dct_bfly32o(out0, out1, a,b,bias,s) \\\n      { \\\n         __m128i abiased_l = _mm_add_epi32(a##_l, bias); \\\n         __m128i abiased_h = _mm_add_epi32(a##_h, bias); \\\n         dct_wadd(sum, abiased, b); \\\n         dct_wsub(dif, abiased, b); \\\n         out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \\\n         out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \\\n      }\n\n   // 8-bit interleave step (for transposes)\n   #define dct_interleave8(a, b) \\\n      tmp = a; \\\n      a = _mm_unpacklo_epi8(a, b); \\\n      b = _mm_unpackhi_epi8(tmp, b)\n\n   // 16-bit interleave step (for transposes)\n   #define dct_interleave16(a, b) \\\n      tmp = a; \\\n      a = _mm_unpacklo_epi16(a, b); \\\n      b = _mm_unpackhi_epi16(tmp, b)\n\n   #define dct_pass(bias,shift) \\\n      { \\\n         /* even part */ \\\n         dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \\\n         __m128i sum04 = _mm_add_epi16(row0, row4); \\\n         __m128i dif04 = _mm_sub_epi16(row0, row4); \\\n         dct_widen(t0e, sum04); \\\n         dct_widen(t1e, dif04); \\\n         dct_wadd(x0, t0e, t3e); \\\n         dct_wsub(x3, t0e, t3e); \\\n         dct_wadd(x1, t1e, t2e); \\\n         dct_wsub(x2, t1e, t2e); \\\n         /* odd part */ \\\n         dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \\\n         dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \\\n         __m128i sum17 = _mm_add_epi16(row1, row7); \\\n         __m128i sum35 = _mm_add_epi16(row3, row5); \\\n         dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \\\n         dct_wadd(x4, y0o, y4o); \\\n         dct_wadd(x5, y1o, y5o); \\\n         dct_wadd(x6, y2o, y5o); \\\n         dct_wadd(x7, y3o, y4o); \\\n         dct_bfly32o(row0,row7, x0,x7,bias,shift); \\\n         dct_bfly32o(row1,row6, x1,x6,bias,shift); \\\n         dct_bfly32o(row2,row5, x2,x5,bias,shift); \\\n         dct_bfly32o(row3,row4, x3,x4,bias,shift); \\\n      }\n\n   __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));\n   __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));\n   __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));\n   __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));\n   __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));\n   __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));\n   __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));\n   __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));\n\n   // rounding biases in column/row passes, see stbi__idct_block for explanation.\n   __m128i bias_0 = _mm_set1_epi32(512);\n   __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));\n\n   // load\n   row0 = _mm_load_si128((const __m128i *) (data + 0*8));\n   row1 = _mm_load_si128((const __m128i *) (data + 1*8));\n   row2 = _mm_load_si128((const __m128i *) (data + 2*8));\n   row3 = _mm_load_si128((const __m128i *) (data + 3*8));\n   row4 = _mm_load_si128((const __m128i *) (data + 4*8));\n   row5 = _mm_load_si128((const __m128i *) (data + 5*8));\n   row6 = _mm_load_si128((const __m128i *) (data + 6*8));\n   row7 = _mm_load_si128((const __m128i *) (data + 7*8));\n\n   // column pass\n   dct_pass(bias_0, 10);\n\n   {\n      // 16bit 8x8 transpose pass 1\n      dct_interleave16(row0, row4);\n      dct_interleave16(row1, row5);\n      dct_interleave16(row2, row6);\n      dct_interleave16(row3, row7);\n\n      // transpose pass 2\n      dct_interleave16(row0, row2);\n      dct_interleave16(row1, row3);\n      dct_interleave16(row4, row6);\n      dct_interleave16(row5, row7);\n\n      // transpose pass 3\n      dct_interleave16(row0, row1);\n      dct_interleave16(row2, row3);\n      dct_interleave16(row4, row5);\n      dct_interleave16(row6, row7);\n   }\n\n   // row pass\n   dct_pass(bias_1, 17);\n\n   {\n      // pack\n      __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7\n      __m128i p1 = _mm_packus_epi16(row2, row3);\n      __m128i p2 = _mm_packus_epi16(row4, row5);\n      __m128i p3 = _mm_packus_epi16(row6, row7);\n\n      // 8bit 8x8 transpose pass 1\n      dct_interleave8(p0, p2); // a0e0a1e1...\n      dct_interleave8(p1, p3); // c0g0c1g1...\n\n      // transpose pass 2\n      dct_interleave8(p0, p1); // a0c0e0g0...\n      dct_interleave8(p2, p3); // b0d0f0h0...\n\n      // transpose pass 3\n      dct_interleave8(p0, p2); // a0b0c0d0...\n      dct_interleave8(p1, p3); // a4b4c4d4...\n\n      // store\n      _mm_storel_epi64((__m128i *) out, p0); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, p2); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, p1); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, p3); out += ort_stride;\n      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));\n   }\n\n#undef dct_const\n#undef dct_rot\n#undef dct_widen\n#undef dct_wadd\n#undef dct_wsub\n#undef dct_bfly32o\n#undef dct_interleave8\n#undef dct_interleave16\n#undef dct_pass\n}\n\n#endif // STBI_SSE2\n\n#ifdef STBI_NEON\n\n// NEON integer IDCT. should produce bit-identical\n// results to the generic C version.\nstatic void stbi__idct_simd(stbi_uc *out, int ort_stride, short data[64])\n{\n   int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;\n\n   int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));\n   int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));\n   int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));\n   int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));\n   int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));\n   int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));\n   int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));\n   int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));\n   int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));\n   int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));\n   int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));\n   int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));\n\n#define dct_long_mul(out, inq, coeff) \\\n   int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \\\n   int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)\n\n#define dct_long_mac(out, acc, inq, coeff) \\\n   int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \\\n   int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)\n\n#define dct_widen(out, inq) \\\n   int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \\\n   int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)\n\n// wide add\n#define dct_wadd(out, a, b) \\\n   int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \\\n   int32x4_t out##_h = vaddq_s32(a##_h, b##_h)\n\n// wide sub\n#define dct_wsub(out, a, b) \\\n   int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \\\n   int32x4_t out##_h = vsubq_s32(a##_h, b##_h)\n\n// butterfly a/b, then shift using \"shiftop\" by \"s\" and pack\n#define dct_bfly32o(out0,out1, a,b,shiftop,s) \\\n   { \\\n      dct_wadd(sum, a, b); \\\n      dct_wsub(dif, a, b); \\\n      out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \\\n      out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \\\n   }\n\n#define dct_pass(shiftop, shift) \\\n   { \\\n      /* even part */ \\\n      int16x8_t sum26 = vaddq_s16(row2, row6); \\\n      dct_long_mul(p1e, sum26, rot0_0); \\\n      dct_long_mac(t2e, p1e, row6, rot0_1); \\\n      dct_long_mac(t3e, p1e, row2, rot0_2); \\\n      int16x8_t sum04 = vaddq_s16(row0, row4); \\\n      int16x8_t dif04 = vsubq_s16(row0, row4); \\\n      dct_widen(t0e, sum04); \\\n      dct_widen(t1e, dif04); \\\n      dct_wadd(x0, t0e, t3e); \\\n      dct_wsub(x3, t0e, t3e); \\\n      dct_wadd(x1, t1e, t2e); \\\n      dct_wsub(x2, t1e, t2e); \\\n      /* odd part */ \\\n      int16x8_t sum15 = vaddq_s16(row1, row5); \\\n      int16x8_t sum17 = vaddq_s16(row1, row7); \\\n      int16x8_t sum35 = vaddq_s16(row3, row5); \\\n      int16x8_t sum37 = vaddq_s16(row3, row7); \\\n      int16x8_t sumodd = vaddq_s16(sum17, sum35); \\\n      dct_long_mul(p5o, sumodd, rot1_0); \\\n      dct_long_mac(p1o, p5o, sum17, rot1_1); \\\n      dct_long_mac(p2o, p5o, sum35, rot1_2); \\\n      dct_long_mul(p3o, sum37, rot2_0); \\\n      dct_long_mul(p4o, sum15, rot2_1); \\\n      dct_wadd(sump13o, p1o, p3o); \\\n      dct_wadd(sump24o, p2o, p4o); \\\n      dct_wadd(sump23o, p2o, p3o); \\\n      dct_wadd(sump14o, p1o, p4o); \\\n      dct_long_mac(x4, sump13o, row7, rot3_0); \\\n      dct_long_mac(x5, sump24o, row5, rot3_1); \\\n      dct_long_mac(x6, sump23o, row3, rot3_2); \\\n      dct_long_mac(x7, sump14o, row1, rot3_3); \\\n      dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \\\n      dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \\\n      dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \\\n      dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \\\n   }\n\n   // load\n   row0 = vld1q_s16(data + 0*8);\n   row1 = vld1q_s16(data + 1*8);\n   row2 = vld1q_s16(data + 2*8);\n   row3 = vld1q_s16(data + 3*8);\n   row4 = vld1q_s16(data + 4*8);\n   row5 = vld1q_s16(data + 5*8);\n   row6 = vld1q_s16(data + 6*8);\n   row7 = vld1q_s16(data + 7*8);\n\n   // add DC bias\n   row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));\n\n   // column pass\n   dct_pass(vrshrn_n_s32, 10);\n\n   // 16bit 8x8 transpose\n   {\n// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.\n// whether compilers actually get this is another story, sadly.\n#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }\n#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }\n#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }\n\n      // pass 1\n      dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6\n      dct_trn16(row2, row3);\n      dct_trn16(row4, row5);\n      dct_trn16(row6, row7);\n\n      // pass 2\n      dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4\n      dct_trn32(row1, row3);\n      dct_trn32(row4, row6);\n      dct_trn32(row5, row7);\n\n      // pass 3\n      dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0\n      dct_trn64(row1, row5);\n      dct_trn64(row2, row6);\n      dct_trn64(row3, row7);\n\n#undef dct_trn16\n#undef dct_trn32\n#undef dct_trn64\n   }\n\n   // row pass\n   // vrshrn_n_s32 only supports shifts up to 16, we need\n   // 17. so do a non-rounding shift of 16 first then follow\n   // up with a rounding shift by 1.\n   dct_pass(vshrn_n_s32, 16);\n\n   {\n      // pack and round\n      uint8x8_t p0 = vqrshrun_n_s16(row0, 1);\n      uint8x8_t p1 = vqrshrun_n_s16(row1, 1);\n      uint8x8_t p2 = vqrshrun_n_s16(row2, 1);\n      uint8x8_t p3 = vqrshrun_n_s16(row3, 1);\n      uint8x8_t p4 = vqrshrun_n_s16(row4, 1);\n      uint8x8_t p5 = vqrshrun_n_s16(row5, 1);\n      uint8x8_t p6 = vqrshrun_n_s16(row6, 1);\n      uint8x8_t p7 = vqrshrun_n_s16(row7, 1);\n\n      // again, these can translate into one instruction, but often don't.\n#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }\n#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }\n#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }\n\n      // sadly can't use interleaved stores here since we only write\n      // 8 bytes to each scan line!\n\n      // 8x8 8-bit transpose pass 1\n      dct_trn8_8(p0, p1);\n      dct_trn8_8(p2, p3);\n      dct_trn8_8(p4, p5);\n      dct_trn8_8(p6, p7);\n\n      // pass 2\n      dct_trn8_16(p0, p2);\n      dct_trn8_16(p1, p3);\n      dct_trn8_16(p4, p6);\n      dct_trn8_16(p5, p7);\n\n      // pass 3\n      dct_trn8_32(p0, p4);\n      dct_trn8_32(p1, p5);\n      dct_trn8_32(p2, p6);\n      dct_trn8_32(p3, p7);\n\n      // store\n      vst1_u8(out, p0); out += ort_stride;\n      vst1_u8(out, p1); out += ort_stride;\n      vst1_u8(out, p2); out += ort_stride;\n      vst1_u8(out, p3); out += ort_stride;\n      vst1_u8(out, p4); out += ort_stride;\n      vst1_u8(out, p5); out += ort_stride;\n      vst1_u8(out, p6); out += ort_stride;\n      vst1_u8(out, p7);\n\n#undef dct_trn8_8\n#undef dct_trn8_16\n#undef dct_trn8_32\n   }\n\n#undef dct_long_mul\n#undef dct_long_mac\n#undef dct_widen\n#undef dct_wadd\n#undef dct_wsub\n#undef dct_bfly32o\n#undef dct_pass\n}\n\n#endif // STBI_NEON\n\n#define STBI__MARKER_none  0xff\n// if there's a pending marker from the entropy stream, return that\n// otherwise, fetch from the stream and get a marker. if there's no\n// marker, return 0xff, which is never a valid marker value\nstatic stbi_uc stbi__get_marker(stbi__jpeg *j)\n{\n   stbi_uc x;\n   if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }\n   x = stbi__get8(j->s);\n   if (x != 0xff) return STBI__MARKER_none;\n   while (x == 0xff)\n      x = stbi__get8(j->s); // consume repeated 0xff fill bytes\n   return x;\n}\n\n// in each scan, we'll have scan_n components, and the order\n// of the components is specified by order[]\n#define STBI__RESTART(x)     ((x) >= 0xd0 && (x) <= 0xd7)\n\n// after a restart interval, stbi__jpeg_reset the entropy decoder and\n// the dc prediction\nstatic void stbi__jpeg_reset(stbi__jpeg *j)\n{\n   j->code_bits = 0;\n   j->code_buffer = 0;\n   j->nomore = 0;\n   j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;\n   j->marker = STBI__MARKER_none;\n   j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;\n   j->eob_run = 0;\n   // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,\n   // since we don't even allow 1<<30 pixels\n}\n\nstatic int stbi__parse_entropy_coded_data(stbi__jpeg *z)\n{\n   stbi__jpeg_reset(z);\n   if (!z->progressive) {\n      if (z->scan_n == 1) {\n         int i,j;\n         STBI_SIMD_ALIGN(short, data[64]);\n         int n = z->order[0];\n         // non-interleaved data, we just need to process one block at a time,\n         // in trivial scanline order\n         // number of blocks to do just depends on how many actual \"pixels\" this\n         // component has, independent of interleaved MCU blocking and such\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               int ha = z->img_comp[n].ha;\n               if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;\n               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);\n               // every data block is an MCU, so countdown the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  // if it's NOT a restart, then just bail, so we get corrupt data\n                  // rather than no data\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      } else { // interleaved\n         int i,j,k,x,y;\n         STBI_SIMD_ALIGN(short, data[64]);\n         for (j=0; j < z->img_mcu_y; ++j) {\n            for (i=0; i < z->img_mcu_x; ++i) {\n               // scan an interleaved mcu... process scan_n components in order\n               for (k=0; k < z->scan_n; ++k) {\n                  int n = z->order[k];\n                  // scan out an mcu's worth of this component; that's just determined\n                  // by the basic H and V specified for the component\n                  for (y=0; y < z->img_comp[n].v; ++y) {\n                     for (x=0; x < z->img_comp[n].h; ++x) {\n                        int x2 = (i*z->img_comp[n].h + x)*8;\n                        int y2 = (j*z->img_comp[n].v + y)*8;\n                        int ha = z->img_comp[n].ha;\n                        if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;\n                        z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);\n                     }\n                  }\n               }\n               // after all interleaved components, that's an interleaved MCU,\n               // so now count down the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      }\n   } else {\n      if (z->scan_n == 1) {\n         int i,j;\n         int n = z->order[0];\n         // non-interleaved data, we just need to process one block at a time,\n         // in trivial scanline order\n         // number of blocks to do just depends on how many actual \"pixels\" this\n         // component has, independent of interleaved MCU blocking and such\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);\n               if (z->spec_start == 0) {\n                  if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))\n                     return 0;\n               } else {\n                  int ha = z->img_comp[n].ha;\n                  if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))\n                     return 0;\n               }\n               // every data block is an MCU, so countdown the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      } else { // interleaved\n         int i,j,k,x,y;\n         for (j=0; j < z->img_mcu_y; ++j) {\n            for (i=0; i < z->img_mcu_x; ++i) {\n               // scan an interleaved mcu... process scan_n components in order\n               for (k=0; k < z->scan_n; ++k) {\n                  int n = z->order[k];\n                  // scan out an mcu's worth of this component; that's just determined\n                  // by the basic H and V specified for the component\n                  for (y=0; y < z->img_comp[n].v; ++y) {\n                     for (x=0; x < z->img_comp[n].h; ++x) {\n                        int x2 = (i*z->img_comp[n].h + x);\n                        int y2 = (j*z->img_comp[n].v + y);\n                        short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);\n                        if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))\n                           return 0;\n                     }\n                  }\n               }\n               // after all interleaved components, that's an interleaved MCU,\n               // so now count down the restart interval\n               if (--z->todo <= 0) {\n                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);\n                  if (!STBI__RESTART(z->marker)) return 1;\n                  stbi__jpeg_reset(z);\n               }\n            }\n         }\n         return 1;\n      }\n   }\n}\n\nstatic void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)\n{\n   int i;\n   for (i=0; i < 64; ++i)\n      data[i] *= dequant[i];\n}\n\nstatic void stbi__jpeg_finish(stbi__jpeg *z)\n{\n   if (z->progressive) {\n      // dequantize and idct the data\n      int i,j,n;\n      for (n=0; n < z->s->img_n; ++n) {\n         int w = (z->img_comp[n].x+7) >> 3;\n         int h = (z->img_comp[n].y+7) >> 3;\n         for (j=0; j < h; ++j) {\n            for (i=0; i < w; ++i) {\n               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);\n               stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);\n               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);\n            }\n         }\n      }\n   }\n}\n\nstatic int stbi__process_marker(stbi__jpeg *z, int m)\n{\n   int L;\n   switch (m) {\n      case STBI__MARKER_none: // no marker found\n         return stbi__err(\"expected marker\",\"Corrupt JPEG\");\n\n      case 0xDD: // DRI - specify restart interval\n         if (stbi__get16be(z->s) != 4) return stbi__err(\"bad DRI len\",\"Corrupt JPEG\");\n         z->restart_interval = stbi__get16be(z->s);\n         return 1;\n\n      case 0xDB: // DQT - define quantization table\n         L = stbi__get16be(z->s)-2;\n         while (L > 0) {\n            int q = stbi__get8(z->s);\n            int p = q >> 4, sixteen = (p != 0);\n            int t = q & 15,i;\n            if (p != 0 && p != 1) return stbi__err(\"bad DQT type\",\"Corrupt JPEG\");\n            if (t > 3) return stbi__err(\"bad DQT table\",\"Corrupt JPEG\");\n\n            for (i=0; i < 64; ++i)\n               z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));\n            L -= (sixteen ? 129 : 65);\n         }\n         return L==0;\n\n      case 0xC4: // DHT - define huffman table\n         L = stbi__get16be(z->s)-2;\n         while (L > 0) {\n            stbi_uc *v;\n            int sizes[16],i,n=0;\n            int q = stbi__get8(z->s);\n            int tc = q >> 4;\n            int th = q & 15;\n            if (tc > 1 || th > 3) return stbi__err(\"bad DHT header\",\"Corrupt JPEG\");\n            for (i=0; i < 16; ++i) {\n               sizes[i] = stbi__get8(z->s);\n               n += sizes[i];\n            }\n            if(n > 256) return stbi__err(\"bad DHT header\",\"Corrupt JPEG\"); // Loop over i < n would write past end of values!\n            L -= 17;\n            if (tc == 0) {\n               if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;\n               v = z->huff_dc[th].values;\n            } else {\n               if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;\n               v = z->huff_ac[th].values;\n            }\n            for (i=0; i < n; ++i)\n               v[i] = stbi__get8(z->s);\n            if (tc != 0)\n               stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);\n            L -= n;\n         }\n         return L==0;\n   }\n\n   // check for comment block or APP blocks\n   if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {\n      L = stbi__get16be(z->s);\n      if (L < 2) {\n         if (m == 0xFE)\n            return stbi__err(\"bad COM len\",\"Corrupt JPEG\");\n         else\n            return stbi__err(\"bad APP len\",\"Corrupt JPEG\");\n      }\n      L -= 2;\n\n      if (m == 0xE0 && L >= 5) { // JFIF APP0 segment\n         static const unsigned char tag[5] = {'J','F','I','F','\\0'};\n         int ok = 1;\n         int i;\n         for (i=0; i < 5; ++i)\n            if (stbi__get8(z->s) != tag[i])\n               ok = 0;\n         L -= 5;\n         if (ok)\n            z->jfif = 1;\n      } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment\n         static const unsigned char tag[6] = {'A','d','o','b','e','\\0'};\n         int ok = 1;\n         int i;\n         for (i=0; i < 6; ++i)\n            if (stbi__get8(z->s) != tag[i])\n               ok = 0;\n         L -= 6;\n         if (ok) {\n            stbi__get8(z->s); // version\n            stbi__get16be(z->s); // flags0\n            stbi__get16be(z->s); // flags1\n            z->app14_color_transform = stbi__get8(z->s); // color transform\n            L -= 6;\n         }\n      }\n\n      stbi__skip(z->s, L);\n      return 1;\n   }\n\n   return stbi__err(\"unknown marker\",\"Corrupt JPEG\");\n}\n\n// after we see SOS\nstatic int stbi__process_scan_header(stbi__jpeg *z)\n{\n   int i;\n   int Ls = stbi__get16be(z->s);\n   z->scan_n = stbi__get8(z->s);\n   if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err(\"bad SOS component count\",\"Corrupt JPEG\");\n   if (Ls != 6+2*z->scan_n) return stbi__err(\"bad SOS len\",\"Corrupt JPEG\");\n   for (i=0; i < z->scan_n; ++i) {\n      int id = stbi__get8(z->s), which;\n      int q = stbi__get8(z->s);\n      for (which = 0; which < z->s->img_n; ++which)\n         if (z->img_comp[which].id == id)\n            break;\n      if (which == z->s->img_n) return 0; // no match\n      z->img_comp[which].hd = q >> 4;   if (z->img_comp[which].hd > 3) return stbi__err(\"bad DC huff\",\"Corrupt JPEG\");\n      z->img_comp[which].ha = q & 15;   if (z->img_comp[which].ha > 3) return stbi__err(\"bad AC huff\",\"Corrupt JPEG\");\n      z->order[i] = which;\n   }\n\n   {\n      int aa;\n      z->spec_start = stbi__get8(z->s);\n      z->spec_end   = stbi__get8(z->s); // should be 63, but might be 0\n      aa = stbi__get8(z->s);\n      z->succ_high = (aa >> 4);\n      z->succ_low  = (aa & 15);\n      if (z->progressive) {\n         if (z->spec_start > 63 || z->spec_end > 63  || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)\n            return stbi__err(\"bad SOS\", \"Corrupt JPEG\");\n      } else {\n         if (z->spec_start != 0) return stbi__err(\"bad SOS\",\"Corrupt JPEG\");\n         if (z->succ_high != 0 || z->succ_low != 0) return stbi__err(\"bad SOS\",\"Corrupt JPEG\");\n         z->spec_end = 63;\n      }\n   }\n\n   return 1;\n}\n\nstatic int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)\n{\n   int i;\n   for (i=0; i < ncomp; ++i) {\n      if (z->img_comp[i].raw_data) {\n         STBI_FREE(z->img_comp[i].raw_data);\n         z->img_comp[i].raw_data = NULL;\n         z->img_comp[i].data = NULL;\n      }\n      if (z->img_comp[i].raw_coeff) {\n         STBI_FREE(z->img_comp[i].raw_coeff);\n         z->img_comp[i].raw_coeff = 0;\n         z->img_comp[i].coeff = 0;\n      }\n      if (z->img_comp[i].linebuf) {\n         STBI_FREE(z->img_comp[i].linebuf);\n         z->img_comp[i].linebuf = NULL;\n      }\n   }\n   return why;\n}\n\nstatic int stbi__process_frame_header(stbi__jpeg *z, int scan)\n{\n   stbi__context *s = z->s;\n   int Lf,p,i,q, h_max=1,v_max=1,c;\n   Lf = stbi__get16be(s);         if (Lf < 11) return stbi__err(\"bad SOF len\",\"Corrupt JPEG\"); // JPEG\n   p  = stbi__get8(s);            if (p != 8) return stbi__err(\"only 8-bit\",\"JPEG format not supported: 8-bit only\"); // JPEG baseline\n   s->img_y = stbi__get16be(s);   if (s->img_y == 0) return stbi__err(\"no header height\", \"JPEG format not supported: delayed height\"); // Legal, but we don't handle it--but neither does IJG\n   s->img_x = stbi__get16be(s);   if (s->img_x == 0) return stbi__err(\"0 width\",\"Corrupt JPEG\"); // JPEG requires\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   c = stbi__get8(s);\n   if (c != 3 && c != 1 && c != 4) return stbi__err(\"bad component count\",\"Corrupt JPEG\");\n   s->img_n = c;\n   for (i=0; i < c; ++i) {\n      z->img_comp[i].data = NULL;\n      z->img_comp[i].linebuf = NULL;\n   }\n\n   if (Lf != 8+3*s->img_n) return stbi__err(\"bad SOF len\",\"Corrupt JPEG\");\n\n   z->rgb = 0;\n   for (i=0; i < s->img_n; ++i) {\n      static const unsigned char rgb[3] = { 'R', 'G', 'B' };\n      z->img_comp[i].id = stbi__get8(s);\n      if (s->img_n == 3 && z->img_comp[i].id == rgb[i])\n         ++z->rgb;\n      q = stbi__get8(s);\n      z->img_comp[i].h = (q >> 4);  if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err(\"bad H\",\"Corrupt JPEG\");\n      z->img_comp[i].v = q & 15;    if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err(\"bad V\",\"Corrupt JPEG\");\n      z->img_comp[i].tq = stbi__get8(s);  if (z->img_comp[i].tq > 3) return stbi__err(\"bad TQ\",\"Corrupt JPEG\");\n   }\n\n   if (scan != STBI__SCAN_load) return 1;\n\n   if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err(\"too large\", \"Image too large to decode\");\n\n   for (i=0; i < s->img_n; ++i) {\n      if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;\n      if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;\n   }\n\n   // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios\n   // and I've never seen a non-corrupted JPEG file actually use them\n   for (i=0; i < s->img_n; ++i) {\n      if (h_max % z->img_comp[i].h != 0) return stbi__err(\"bad H\",\"Corrupt JPEG\");\n      if (v_max % z->img_comp[i].v != 0) return stbi__err(\"bad V\",\"Corrupt JPEG\");\n   }\n\n   // compute interleaved mcu info\n   z->img_h_max = h_max;\n   z->img_v_max = v_max;\n   z->img_mcu_w = h_max * 8;\n   z->img_mcu_h = v_max * 8;\n   // these sizes can't be more than 17 bits\n   z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;\n   z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;\n\n   for (i=0; i < s->img_n; ++i) {\n      // number of effective pixels (e.g. for non-interleaved MCU)\n      z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;\n      z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;\n      // to simplify generation, we'll allocate enough memory to decode\n      // the bogus oversized data from using interleaved MCUs and their\n      // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't\n      // discard the extra data until colorspace conversion\n      //\n      // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)\n      // so these muls can't overflow with 32-bit ints (which we require)\n      z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;\n      z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;\n      z->img_comp[i].coeff = 0;\n      z->img_comp[i].raw_coeff = 0;\n      z->img_comp[i].linebuf = NULL;\n      z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);\n      if (z->img_comp[i].raw_data == NULL)\n         return stbi__free_jpeg_components(z, i+1, stbi__err(\"outofmem\", \"Out of memory\"));\n      // align blocks for idct using mmx/sse\n      z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);\n      if (z->progressive) {\n         // w2, h2 are multiples of 8 (see above)\n         z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;\n         z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;\n         z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);\n         if (z->img_comp[i].raw_coeff == NULL)\n            return stbi__free_jpeg_components(z, i+1, stbi__err(\"outofmem\", \"Out of memory\"));\n         z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);\n      }\n   }\n\n   return 1;\n}\n\n// use comparisons since in some cases we handle more than one case (e.g. SOF)\n#define stbi__DNL(x)         ((x) == 0xdc)\n#define stbi__SOI(x)         ((x) == 0xd8)\n#define stbi__EOI(x)         ((x) == 0xd9)\n#define stbi__SOF(x)         ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)\n#define stbi__SOS(x)         ((x) == 0xda)\n\n#define stbi__SOF_progressive(x)   ((x) == 0xc2)\n\nstatic int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)\n{\n   int m;\n   z->jfif = 0;\n   z->app14_color_transform = -1; // valid values are 0,1,2\n   z->marker = STBI__MARKER_none; // initialize cached marker to empty\n   m = stbi__get_marker(z);\n   if (!stbi__SOI(m)) return stbi__err(\"no SOI\",\"Corrupt JPEG\");\n   if (scan == STBI__SCAN_type) return 1;\n   m = stbi__get_marker(z);\n   while (!stbi__SOF(m)) {\n      if (!stbi__process_marker(z,m)) return 0;\n      m = stbi__get_marker(z);\n      while (m == STBI__MARKER_none) {\n         // some files have extra padding after their blocks, so ok, we'll scan\n         if (stbi__at_eof(z->s)) return stbi__err(\"no SOF\", \"Corrupt JPEG\");\n         m = stbi__get_marker(z);\n      }\n   }\n   z->progressive = stbi__SOF_progressive(m);\n   if (!stbi__process_frame_header(z, scan)) return 0;\n   return 1;\n}\n\nstatic stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)\n{\n   // some JPEGs have junk at end, skip over it but if we find what looks\n   // like a valid marker, resume there\n   while (!stbi__at_eof(j->s)) {\n      stbi_uc x = stbi__get8(j->s);\n      while (x == 0xff) { // might be a marker\n         if (stbi__at_eof(j->s)) return STBI__MARKER_none;\n         x = stbi__get8(j->s);\n         if (x != 0x00 && x != 0xff) {\n            // not a stuffed zero or lead-in to another marker, looks\n            // like an actual marker, return it\n            return x;\n         }\n         // stuffed zero has x=0 now which ends the loop, meaning we go\n         // back to regular scan loop.\n         // repeated 0xff keeps trying to read the next byte of the marker.\n      }\n   }\n   return STBI__MARKER_none;\n}\n\n// decode image to YCbCr format\nstatic int stbi__decode_jpeg_image(stbi__jpeg *j)\n{\n   int m;\n   for (m = 0; m < 4; m++) {\n      j->img_comp[m].raw_data = NULL;\n      j->img_comp[m].raw_coeff = NULL;\n   }\n   j->restart_interval = 0;\n   if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;\n   m = stbi__get_marker(j);\n   while (!stbi__EOI(m)) {\n      if (stbi__SOS(m)) {\n         if (!stbi__process_scan_header(j)) return 0;\n         if (!stbi__parse_entropy_coded_data(j)) return 0;\n         if (j->marker == STBI__MARKER_none ) {\n         j->marker = stbi__skip_jpeg_junk_at_end(j);\n            // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0\n         }\n         m = stbi__get_marker(j);\n         if (STBI__RESTART(m))\n            m = stbi__get_marker(j);\n      } else if (stbi__DNL(m)) {\n         int Ld = stbi__get16be(j->s);\n         stbi__uint32 NL = stbi__get16be(j->s);\n         if (Ld != 4) return stbi__err(\"bad DNL len\", \"Corrupt JPEG\");\n         if (NL != j->s->img_y) return stbi__err(\"bad DNL height\", \"Corrupt JPEG\");\n         m = stbi__get_marker(j);\n      } else {\n         if (!stbi__process_marker(j, m)) return 1;\n         m = stbi__get_marker(j);\n      }\n   }\n   if (j->progressive)\n      stbi__jpeg_finish(j);\n   return 1;\n}\n\n// static jfif-centered resampling (across block boundaries)\n\ntypedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,\n                                    int w, int hs);\n\n#define stbi__div4(x) ((stbi_uc) ((x) >> 2))\n\nstatic stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   STBI_NOTUSED(out);\n   STBI_NOTUSED(in_far);\n   STBI_NOTUSED(w);\n   STBI_NOTUSED(hs);\n   return in_near;\n}\n\nstatic stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate two samples vertically for every one in input\n   int i;\n   STBI_NOTUSED(hs);\n   for (i=0; i < w; ++i)\n      out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);\n   return out;\n}\n\nstatic stbi_uc*  stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate two samples horizontally for every one in input\n   int i;\n   stbi_uc *input = in_near;\n\n   if (w == 1) {\n      // if only one sample, can't do any interpolation\n      out[0] = out[1] = input[0];\n      return out;\n   }\n\n   out[0] = input[0];\n   out[1] = stbi__div4(input[0]*3 + input[1] + 2);\n   for (i=1; i < w-1; ++i) {\n      int n = 3*input[i]+2;\n      out[i*2+0] = stbi__div4(n+input[i-1]);\n      out[i*2+1] = stbi__div4(n+input[i+1]);\n   }\n   out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);\n   out[i*2+1] = input[w-1];\n\n   STBI_NOTUSED(in_far);\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n\n#define stbi__div16(x) ((stbi_uc) ((x) >> 4))\n\nstatic stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate 2x2 samples for every one in input\n   int i,t0,t1;\n   if (w == 1) {\n      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);\n      return out;\n   }\n\n   t1 = 3*in_near[0] + in_far[0];\n   out[0] = stbi__div4(t1+2);\n   for (i=1; i < w; ++i) {\n      t0 = t1;\n      t1 = 3*in_near[i]+in_far[i];\n      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);\n      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);\n   }\n   out[w*2-1] = stbi__div4(t1+2);\n\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n\n#if defined(STBI_SSE2) || defined(STBI_NEON)\nstatic stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // need to generate 2x2 samples for every one in input\n   int i=0,t0,t1;\n\n   if (w == 1) {\n      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);\n      return out;\n   }\n\n   t1 = 3*in_near[0] + in_far[0];\n   // process groups of 8 pixels for as long as we can.\n   // note we can't handle the last pixel in a row in this loop\n   // because we need to handle the filter boundary conditions.\n   for (; i < ((w-1) & ~7); i += 8) {\n#if defined(STBI_SSE2)\n      // load and perform the vertical filtering pass\n      // this uses 3*x + y = 4*x + (y - x)\n      __m128i zero  = _mm_setzero_si128();\n      __m128i farb  = _mm_loadl_epi64((__m128i *) (in_far + i));\n      __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));\n      __m128i farw  = _mm_unpacklo_epi8(farb, zero);\n      __m128i nearw = _mm_unpacklo_epi8(nearb, zero);\n      __m128i diff  = _mm_sub_epi16(farw, nearw);\n      __m128i nears = _mm_slli_epi16(nearw, 2);\n      __m128i curr  = _mm_add_epi16(nears, diff); // current row\n\n      // horizontal filter works the same based on shifted vers of current\n      // row. \"prev\" is current row shifted right by 1 pixel; we need to\n      // insert the previous pixel value (from t1).\n      // \"next\" is current row shifted left by 1 pixel, with first pixel\n      // of next block of 8 pixels added in.\n      __m128i prv0 = _mm_slli_si128(curr, 2);\n      __m128i nxt0 = _mm_srli_si128(curr, 2);\n      __m128i prev = _mm_insert_epi16(prv0, t1, 0);\n      __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);\n\n      // horizontal filter, polyphase implementation since it's convenient:\n      // even pixels = 3*cur + prev = cur*4 + (prev - cur)\n      // odd  pixels = 3*cur + next = cur*4 + (next - cur)\n      // note the shared term.\n      __m128i bias  = _mm_set1_epi16(8);\n      __m128i curs = _mm_slli_epi16(curr, 2);\n      __m128i prvd = _mm_sub_epi16(prev, curr);\n      __m128i nxtd = _mm_sub_epi16(next, curr);\n      __m128i curb = _mm_add_epi16(curs, bias);\n      __m128i even = _mm_add_epi16(prvd, curb);\n      __m128i odd  = _mm_add_epi16(nxtd, curb);\n\n      // interleave even and odd pixels, then undo scaling.\n      __m128i int0 = _mm_unpacklo_epi16(even, odd);\n      __m128i int1 = _mm_unpackhi_epi16(even, odd);\n      __m128i de0  = _mm_srli_epi16(int0, 4);\n      __m128i de1  = _mm_srli_epi16(int1, 4);\n\n      // pack and write output\n      __m128i outv = _mm_packus_epi16(de0, de1);\n      _mm_storeu_si128((__m128i *) (out + i*2), outv);\n#elif defined(STBI_NEON)\n      // load and perform the vertical filtering pass\n      // this uses 3*x + y = 4*x + (y - x)\n      uint8x8_t farb  = vld1_u8(in_far + i);\n      uint8x8_t nearb = vld1_u8(in_near + i);\n      int16x8_t diff  = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));\n      int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));\n      int16x8_t curr  = vaddq_s16(nears, diff); // current row\n\n      // horizontal filter works the same based on shifted vers of current\n      // row. \"prev\" is current row shifted right by 1 pixel; we need to\n      // insert the previous pixel value (from t1).\n      // \"next\" is current row shifted left by 1 pixel, with first pixel\n      // of next block of 8 pixels added in.\n      int16x8_t prv0 = vextq_s16(curr, curr, 7);\n      int16x8_t nxt0 = vextq_s16(curr, curr, 1);\n      int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);\n      int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);\n\n      // horizontal filter, polyphase implementation since it's convenient:\n      // even pixels = 3*cur + prev = cur*4 + (prev - cur)\n      // odd  pixels = 3*cur + next = cur*4 + (next - cur)\n      // note the shared term.\n      int16x8_t curs = vshlq_n_s16(curr, 2);\n      int16x8_t prvd = vsubq_s16(prev, curr);\n      int16x8_t nxtd = vsubq_s16(next, curr);\n      int16x8_t even = vaddq_s16(curs, prvd);\n      int16x8_t odd  = vaddq_s16(curs, nxtd);\n\n      // undo scaling and round, then store with even/odd phases interleaved\n      uint8x8x2_t o;\n      o.val[0] = vqrshrun_n_s16(even, 4);\n      o.val[1] = vqrshrun_n_s16(odd,  4);\n      vst2_u8(out + i*2, o);\n#endif\n\n      // \"previous\" value for next iter\n      t1 = 3*in_near[i+7] + in_far[i+7];\n   }\n\n   t0 = t1;\n   t1 = 3*in_near[i] + in_far[i];\n   out[i*2] = stbi__div16(3*t1 + t0 + 8);\n\n   for (++i; i < w; ++i) {\n      t0 = t1;\n      t1 = 3*in_near[i]+in_far[i];\n      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);\n      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);\n   }\n   out[w*2-1] = stbi__div4(t1+2);\n\n   STBI_NOTUSED(hs);\n\n   return out;\n}\n#endif\n\nstatic stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)\n{\n   // resample with nearest-neighbor\n   int i,j;\n   STBI_NOTUSED(in_far);\n   for (i=0; i < w; ++i)\n      for (j=0; j < hs; ++j)\n         out[i*hs+j] = in_near[i];\n   return out;\n}\n\n// this is a reduced-precision calculation of YCbCr-to-RGB introduced\n// to make sure the code produces the same results in both SIMD and scalar\n#define stbi__float2fixed(x)  (((int) ((x) * 4096.0f + 0.5f)) << 8)\nstatic void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)\n{\n   int i;\n   for (i=0; i < count; ++i) {\n      int y_fixed = (y[i] << 20) + (1<<19); // rounding\n      int r,g,b;\n      int cr = pcr[i] - 128;\n      int cb = pcb[i] - 128;\n      r = y_fixed +  cr* stbi__float2fixed(1.40200f);\n      g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);\n      b = y_fixed                                     +   cb* stbi__float2fixed(1.77200f);\n      r >>= 20;\n      g >>= 20;\n      b >>= 20;\n      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }\n      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }\n      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }\n      out[0] = (stbi_uc)r;\n      out[1] = (stbi_uc)g;\n      out[2] = (stbi_uc)b;\n      out[3] = 255;\n      out += step;\n   }\n}\n\n#if defined(STBI_SSE2) || defined(STBI_NEON)\nstatic void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)\n{\n   int i = 0;\n\n#ifdef STBI_SSE2\n   // step == 3 is pretty ugly on the final interleave, and i'm not convinced\n   // it's useful in practice (you wouldn't use it for textures, for example).\n   // so just accelerate step == 4 case.\n   if (step == 4) {\n      // this is a fairly straightforward implementation and not super-optimized.\n      __m128i signflip  = _mm_set1_epi8(-0x80);\n      __m128i cr_const0 = _mm_set1_epi16(   (short) ( 1.40200f*4096.0f+0.5f));\n      __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));\n      __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));\n      __m128i cb_const1 = _mm_set1_epi16(   (short) ( 1.77200f*4096.0f+0.5f));\n      __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);\n      __m128i xw = _mm_set1_epi16(255); // alpha channel\n\n      for (; i+7 < count; i += 8) {\n         // load\n         __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));\n         __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));\n         __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));\n         __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128\n         __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128\n\n         // unpack to short (and left-shift cr, cb by 8)\n         __m128i yw  = _mm_unpacklo_epi8(y_bias, y_bytes);\n         __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);\n         __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);\n\n         // color transform\n         __m128i yws = _mm_srli_epi16(yw, 4);\n         __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);\n         __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);\n         __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);\n         __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);\n         __m128i rws = _mm_add_epi16(cr0, yws);\n         __m128i gwt = _mm_add_epi16(cb0, yws);\n         __m128i bws = _mm_add_epi16(yws, cb1);\n         __m128i gws = _mm_add_epi16(gwt, cr1);\n\n         // descale\n         __m128i rw = _mm_srai_epi16(rws, 4);\n         __m128i bw = _mm_srai_epi16(bws, 4);\n         __m128i gw = _mm_srai_epi16(gws, 4);\n\n         // back to byte, set up for transpose\n         __m128i brb = _mm_packus_epi16(rw, bw);\n         __m128i gxb = _mm_packus_epi16(gw, xw);\n\n         // transpose to interleave channels\n         __m128i t0 = _mm_unpacklo_epi8(brb, gxb);\n         __m128i t1 = _mm_unpackhi_epi8(brb, gxb);\n         __m128i o0 = _mm_unpacklo_epi16(t0, t1);\n         __m128i o1 = _mm_unpackhi_epi16(t0, t1);\n\n         // store\n         _mm_storeu_si128((__m128i *) (out + 0), o0);\n         _mm_storeu_si128((__m128i *) (out + 16), o1);\n         out += 32;\n      }\n   }\n#endif\n\n#ifdef STBI_NEON\n   // in this version, step=3 support would be easy to add. but is there demand?\n   if (step == 4) {\n      // this is a fairly straightforward implementation and not super-optimized.\n      uint8x8_t signflip = vdup_n_u8(0x80);\n      int16x8_t cr_const0 = vdupq_n_s16(   (short) ( 1.40200f*4096.0f+0.5f));\n      int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));\n      int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));\n      int16x8_t cb_const1 = vdupq_n_s16(   (short) ( 1.77200f*4096.0f+0.5f));\n\n      for (; i+7 < count; i += 8) {\n         // load\n         uint8x8_t y_bytes  = vld1_u8(y + i);\n         uint8x8_t cr_bytes = vld1_u8(pcr + i);\n         uint8x8_t cb_bytes = vld1_u8(pcb + i);\n         int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));\n         int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));\n\n         // expand to s16\n         int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));\n         int16x8_t crw = vshll_n_s8(cr_biased, 7);\n         int16x8_t cbw = vshll_n_s8(cb_biased, 7);\n\n         // color transform\n         int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);\n         int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);\n         int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);\n         int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);\n         int16x8_t rws = vaddq_s16(yws, cr0);\n         int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);\n         int16x8_t bws = vaddq_s16(yws, cb1);\n\n         // undo scaling, round, convert to byte\n         uint8x8x4_t o;\n         o.val[0] = vqrshrun_n_s16(rws, 4);\n         o.val[1] = vqrshrun_n_s16(gws, 4);\n         o.val[2] = vqrshrun_n_s16(bws, 4);\n         o.val[3] = vdup_n_u8(255);\n\n         // store, interleaving r/g/b/a\n         vst4_u8(out, o);\n         out += 8*4;\n      }\n   }\n#endif\n\n   for (; i < count; ++i) {\n      int y_fixed = (y[i] << 20) + (1<<19); // rounding\n      int r,g,b;\n      int cr = pcr[i] - 128;\n      int cb = pcb[i] - 128;\n      r = y_fixed + cr* stbi__float2fixed(1.40200f);\n      g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);\n      b = y_fixed                                   +   cb* stbi__float2fixed(1.77200f);\n      r >>= 20;\n      g >>= 20;\n      b >>= 20;\n      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }\n      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }\n      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }\n      out[0] = (stbi_uc)r;\n      out[1] = (stbi_uc)g;\n      out[2] = (stbi_uc)b;\n      out[3] = 255;\n      out += step;\n   }\n}\n#endif\n\n// set up the kernels\nstatic void stbi__setup_jpeg(stbi__jpeg *j)\n{\n   j->idct_block_kernel = stbi__idct_block;\n   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;\n   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;\n\n#ifdef STBI_SSE2\n   if (stbi__sse2_available()) {\n      j->idct_block_kernel = stbi__idct_simd;\n      j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;\n      j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;\n   }\n#endif\n\n#ifdef STBI_NEON\n   j->idct_block_kernel = stbi__idct_simd;\n   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;\n   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;\n#endif\n}\n\n// clean up the temporary component buffers\nstatic void stbi__cleanup_jpeg(stbi__jpeg *j)\n{\n   stbi__free_jpeg_components(j, j->s->img_n, 0);\n}\n\ntypedef struct\n{\n   resample_row_func resample;\n   stbi_uc *line0,*line1;\n   int hs,vs;   // expansion factor in each axis\n   int w_lores; // horizontal pixels pre-expansion\n   int ystep;   // how far through vertical expansion we are\n   int ypos;    // which pre-expansion row we're on\n} stbi__resample;\n\n// fast 0..255 * 0..255 => 0..255 rounded multiplication\nstatic stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)\n{\n   unsigned int t = x*y + 128;\n   return (stbi_uc) ((t + (t >>8)) >> 8);\n}\n\nstatic stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)\n{\n   int n, decode_n, is_rgb;\n   z->s->img_n = 0; // make stbi__cleanup_jpeg safe\n\n   // validate req_comp\n   if (req_comp < 0 || req_comp > 4) return stbi__errpuc(\"bad req_comp\", \"Internal error\");\n\n   // load a jpeg image from whichever source, but leave in YCbCr format\n   if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }\n\n   // determine actual number of components to generate\n   n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;\n\n   is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));\n\n   if (z->s->img_n == 3 && n < 3 && !is_rgb)\n      decode_n = 1;\n   else\n      decode_n = z->s->img_n;\n\n   // nothing to do if no components requested; check this now to avoid\n   // accessing uninitialized coutput[0] later\n   if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; }\n\n   // resample and color-convert\n   {\n      int k;\n      unsigned int i,j;\n      stbi_uc *output;\n      stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };\n\n      stbi__resample res_comp[4];\n\n      for (k=0; k < decode_n; ++k) {\n         stbi__resample *r = &res_comp[k];\n\n         // allocate line buffer big enough for upsampling off the edges\n         // with upsample factor of 4\n         z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);\n         if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n\n         r->hs      = z->img_h_max / z->img_comp[k].h;\n         r->vs      = z->img_v_max / z->img_comp[k].v;\n         r->ystep   = r->vs >> 1;\n         r->w_lores = (z->s->img_x + r->hs-1) / r->hs;\n         r->ypos    = 0;\n         r->line0   = r->line1 = z->img_comp[k].data;\n\n         if      (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;\n         else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;\n         else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;\n         else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;\n         else                               r->resample = stbi__resample_row_generic;\n      }\n\n      // can't error after this so, this is safe\n      output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);\n      if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc(\"outofmem\", \"Out of memory\"); }\n\n      // now go ahead and resample\n      for (j=0; j < z->s->img_y; ++j) {\n         stbi_uc *out = output + n * z->s->img_x * j;\n         for (k=0; k < decode_n; ++k) {\n            stbi__resample *r = &res_comp[k];\n            int y_bot = r->ystep >= (r->vs >> 1);\n            coutput[k] = r->resample(z->img_comp[k].linebuf,\n                                     y_bot ? r->line1 : r->line0,\n                                     y_bot ? r->line0 : r->line1,\n                                     r->w_lores, r->hs);\n            if (++r->ystep >= r->vs) {\n               r->ystep = 0;\n               r->line0 = r->line1;\n               if (++r->ypos < z->img_comp[k].y)\n                  r->line1 += z->img_comp[k].w2;\n            }\n         }\n         if (n >= 3) {\n            stbi_uc *y = coutput[0];\n            if (z->s->img_n == 3) {\n               if (is_rgb) {\n                  for (i=0; i < z->s->img_x; ++i) {\n                     out[0] = y[i];\n                     out[1] = coutput[1][i];\n                     out[2] = coutput[2][i];\n                     out[3] = 255;\n                     out += n;\n                  }\n               } else {\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n               }\n            } else if (z->s->img_n == 4) {\n               if (z->app14_color_transform == 0) { // CMYK\n                  for (i=0; i < z->s->img_x; ++i) {\n                     stbi_uc m = coutput[3][i];\n                     out[0] = stbi__blinn_8x8(coutput[0][i], m);\n                     out[1] = stbi__blinn_8x8(coutput[1][i], m);\n                     out[2] = stbi__blinn_8x8(coutput[2][i], m);\n                     out[3] = 255;\n                     out += n;\n                  }\n               } else if (z->app14_color_transform == 2) { // YCCK\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n                  for (i=0; i < z->s->img_x; ++i) {\n                     stbi_uc m = coutput[3][i];\n                     out[0] = stbi__blinn_8x8(255 - out[0], m);\n                     out[1] = stbi__blinn_8x8(255 - out[1], m);\n                     out[2] = stbi__blinn_8x8(255 - out[2], m);\n                     out += n;\n                  }\n               } else { // YCbCr + alpha?  Ignore the fourth channel for now\n                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);\n               }\n            } else\n               for (i=0; i < z->s->img_x; ++i) {\n                  out[0] = out[1] = out[2] = y[i];\n                  out[3] = 255; // not used if n==3\n                  out += n;\n               }\n         } else {\n            if (is_rgb) {\n               if (n == 1)\n                  for (i=0; i < z->s->img_x; ++i)\n                     *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);\n               else {\n                  for (i=0; i < z->s->img_x; ++i, out += 2) {\n                     out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);\n                     out[1] = 255;\n                  }\n               }\n            } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {\n               for (i=0; i < z->s->img_x; ++i) {\n                  stbi_uc m = coutput[3][i];\n                  stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);\n                  stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);\n                  stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);\n                  out[0] = stbi__compute_y(r, g, b);\n                  out[1] = 255;\n                  out += n;\n               }\n            } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {\n               for (i=0; i < z->s->img_x; ++i) {\n                  out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);\n                  out[1] = 255;\n                  out += n;\n               }\n            } else {\n               stbi_uc *y = coutput[0];\n               if (n == 1)\n                  for (i=0; i < z->s->img_x; ++i) out[i] = y[i];\n               else\n                  for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }\n            }\n         }\n      }\n      stbi__cleanup_jpeg(z);\n      *out_x = z->s->img_x;\n      *out_y = z->s->img_y;\n      if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output\n      return output;\n   }\n}\n\nstatic void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   unsigned char* result;\n   stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));\n   if (!j) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   memset(j, 0, sizeof(stbi__jpeg));\n   STBI_NOTUSED(ri);\n   j->s = s;\n   stbi__setup_jpeg(j);\n   result = load_jpeg_image(j, x,y,comp,req_comp);\n   STBI_FREE(j);\n   return result;\n}\n\nstatic int stbi__jpeg_test(stbi__context *s)\n{\n   int r;\n   stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));\n   if (!j) return stbi__err(\"outofmem\", \"Out of memory\");\n   memset(j, 0, sizeof(stbi__jpeg));\n   j->s = s;\n   stbi__setup_jpeg(j);\n   r = stbi__decode_jpeg_header(j, STBI__SCAN_type);\n   stbi__rewind(s);\n   STBI_FREE(j);\n   return r;\n}\n\nstatic int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)\n{\n   if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {\n      stbi__rewind( j->s );\n      return 0;\n   }\n   if (x) *x = j->s->img_x;\n   if (y) *y = j->s->img_y;\n   if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;\n   return 1;\n}\n\nstatic int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int result;\n   stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));\n   if (!j) return stbi__err(\"outofmem\", \"Out of memory\");\n   memset(j, 0, sizeof(stbi__jpeg));\n   j->s = s;\n   result = stbi__jpeg_info_raw(j, x, y, comp);\n   STBI_FREE(j);\n   return result;\n}\n#endif\n\n// public domain zlib decode    v0.2  Sean Barrett 2006-11-18\n//    simple implementation\n//      - all input must be provided in an upfront buffer\n//      - all output is written to a single output buffer (can malloc/realloc)\n//    performance\n//      - fast huffman\n\n#ifndef STBI_NO_ZLIB\n\n// fast-way is faster to check than jpeg huffman, but slow way is slower\n#define STBI__ZFAST_BITS  9 // accelerate all cases in default tables\n#define STBI__ZFAST_MASK  ((1 << STBI__ZFAST_BITS) - 1)\n#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet\n\n// zlib-style huffman encoding\n// (jpegs packs from left, zlib from right, so can't share code)\ntypedef struct\n{\n   stbi__uint16 fast[1 << STBI__ZFAST_BITS];\n   stbi__uint16 firstcode[16];\n   int maxcode[17];\n   stbi__uint16 firstsymbol[16];\n   stbi_uc  size[STBI__ZNSYMS];\n   stbi__uint16 value[STBI__ZNSYMS];\n} stbi__zhuffman;\n\nstbi_inline static int stbi__bitreverse16(int n)\n{\n  n = ((n & 0xAAAA) >>  1) | ((n & 0x5555) << 1);\n  n = ((n & 0xCCCC) >>  2) | ((n & 0x3333) << 2);\n  n = ((n & 0xF0F0) >>  4) | ((n & 0x0F0F) << 4);\n  n = ((n & 0xFF00) >>  8) | ((n & 0x00FF) << 8);\n  return n;\n}\n\nstbi_inline static int stbi__bit_reverse(int v, int bits)\n{\n   STBI_ASSERT(bits <= 16);\n   // to bit reverse n bits, reverse 16 and shift\n   // e.g. 11 bits, bit reverse and shift away 5\n   return stbi__bitreverse16(v) >> (16-bits);\n}\n\nstatic int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)\n{\n   int i,k=0;\n   int code, next_code[16], sizes[17];\n\n   // DEFLATE spec for generating codes\n   memset(sizes, 0, sizeof(sizes));\n   memset(z->fast, 0, sizeof(z->fast));\n   for (i=0; i < num; ++i)\n      ++sizes[sizelist[i]];\n   sizes[0] = 0;\n   for (i=1; i < 16; ++i)\n      if (sizes[i] > (1 << i))\n         return stbi__err(\"bad sizes\", \"Corrupt PNG\");\n   code = 0;\n   for (i=1; i < 16; ++i) {\n      next_code[i] = code;\n      z->firstcode[i] = (stbi__uint16) code;\n      z->firstsymbol[i] = (stbi__uint16) k;\n      code = (code + sizes[i]);\n      if (sizes[i])\n         if (code-1 >= (1 << i)) return stbi__err(\"bad codelengths\",\"Corrupt PNG\");\n      z->maxcode[i] = code << (16-i); // preshift for inner loop\n      code <<= 1;\n      k += sizes[i];\n   }\n   z->maxcode[16] = 0x10000; // sentinel\n   for (i=0; i < num; ++i) {\n      int s = sizelist[i];\n      if (s) {\n         int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];\n         stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);\n         z->size [c] = (stbi_uc     ) s;\n         z->value[c] = (stbi__uint16) i;\n         if (s <= STBI__ZFAST_BITS) {\n            int j = stbi__bit_reverse(next_code[s],s);\n            while (j < (1 << STBI__ZFAST_BITS)) {\n               z->fast[j] = fastv;\n               j += (1 << s);\n            }\n         }\n         ++next_code[s];\n      }\n   }\n   return 1;\n}\n\n// zlib-from-memory implementation for PNG reading\n//    because PNG allows splitting the zlib stream arbitrarily,\n//    and it's annoying structurally to have PNG call ZLIB call PNG,\n//    we require PNG read all the IDATs and combine them into a single\n//    memory buffer\n\ntypedef struct\n{\n   stbi_uc *zbuffer, *zbuffer_end;\n   int num_bits;\n   int hit_zeof_once;\n   stbi__uint32 code_buffer;\n\n   char *zout;\n   char *zout_start;\n   char *zort_end;\n   int   z_expandable;\n\n   stbi__zhuffman z_length, z_distance;\n} stbi__zbuf;\n\nstbi_inline static int stbi__zeof(stbi__zbuf *z)\n{\n   return (z->zbuffer >= z->zbuffer_end);\n}\n\nstbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)\n{\n   return stbi__zeof(z) ? 0 : *z->zbuffer++;\n}\n\nstatic void stbi__fill_bits(stbi__zbuf *z)\n{\n   do {\n      if (z->code_buffer >= (1U << z->num_bits)) {\n        z->zbuffer = z->zbuffer_end;  /* treat this as EOF so we fail. */\n        return;\n      }\n      z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;\n      z->num_bits += 8;\n   } while (z->num_bits <= 24);\n}\n\nstbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)\n{\n   unsigned int k;\n   if (z->num_bits < n) stbi__fill_bits(z);\n   k = z->code_buffer & ((1 << n) - 1);\n   z->code_buffer >>= n;\n   z->num_bits -= n;\n   return k;\n}\n\nstatic int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)\n{\n   int b,s,k;\n   // not resolved by fast table, so compute it the slow way\n   // use jpeg approach, which requires MSbits at top\n   k = stbi__bit_reverse(a->code_buffer, 16);\n   for (s=STBI__ZFAST_BITS+1; ; ++s)\n      if (k < z->maxcode[s])\n         break;\n   if (s >= 16) return -1; // invalid code!\n   // code size is s, so:\n   b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];\n   if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere!\n   if (z->size[b] != s) return -1;  // was originally an assert, but report failure instead.\n   a->code_buffer >>= s;\n   a->num_bits -= s;\n   return z->value[b];\n}\n\nstbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)\n{\n   int b,s;\n   if (a->num_bits < 16) {\n      if (stbi__zeof(a)) {\n         if (!a->hit_zeof_once) {\n            // This is the first time we hit eof, insert 16 extra padding btis\n            // to allow us to keep going; if we actually consume any of them\n            // though, that is invalid data. This is caught later.\n            a->hit_zeof_once = 1;\n            a->num_bits += 16; // add 16 implicit zero bits\n         } else {\n            // We already inserted our extra 16 padding bits and are again\n            // out, this stream is actually prematurely terminated.\n            return -1;\n         }\n      } else {\n         stbi__fill_bits(a);\n      }\n   }\n   b = z->fast[a->code_buffer & STBI__ZFAST_MASK];\n   if (b) {\n      s = b >> 9;\n      a->code_buffer >>= s;\n      a->num_bits -= s;\n      return b & 511;\n   }\n   return stbi__zhuffman_decode_slowpath(a, z);\n}\n\nstatic int stbi__zexpand(stbi__zbuf *z, char *zout, int n)  // need to make room for n bytes\n{\n   char *q;\n   unsigned int cur, limit, old_limit;\n   z->zout = zout;\n   if (!z->z_expandable) return stbi__err(\"output buffer limit\",\"Corrupt PNG\");\n   cur   = (unsigned int) (z->zout - z->zout_start);\n   limit = old_limit = (unsigned) (z->zort_end - z->zout_start);\n   if (UINT_MAX - cur < (unsigned) n) return stbi__err(\"outofmem\", \"Out of memory\");\n   while (cur + n > limit) {\n      if(limit > UINT_MAX / 2) return stbi__err(\"outofmem\", \"Out of memory\");\n      limit *= 2;\n   }\n   q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);\n   STBI_NOTUSED(old_limit);\n   if (q == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n   z->zout_start = q;\n   z->zout       = q + cur;\n   z->zort_end   = q + limit;\n   return 1;\n}\n\nstatic const int stbi__zlength_base[31] = {\n   3,4,5,6,7,8,9,10,11,13,\n   15,17,19,23,27,31,35,43,51,59,\n   67,83,99,115,131,163,195,227,258,0,0 };\n\nstatic const int stbi__zlength_extra[31]=\n{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };\n\nstatic const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,\n257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};\n\nstatic const int stbi__zdist_extra[32] =\n{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};\n\nstatic int stbi__parse_huffman_block(stbi__zbuf *a)\n{\n   char *zout = a->zout;\n   for(;;) {\n      int z = stbi__zhuffman_decode(a, &a->z_length);\n      if (z < 256) {\n         if (z < 0) return stbi__err(\"bad huffman code\",\"Corrupt PNG\"); // error in huffman codes\n         if (zout >= a->zort_end) {\n            if (!stbi__zexpand(a, zout, 1)) return 0;\n            zout = a->zout;\n         }\n         *zout++ = (char) z;\n      } else {\n         stbi_uc *p;\n         int len,dist;\n         if (z == 256) {\n            a->zout = zout;\n            if (a->hit_zeof_once && a->num_bits < 16) {\n               // The first time we hit zeof, we inserted 16 extra zero bits into our bit\n               // buffer so the decoder can just do its speculative decoding. But if we\n               // actually consumed any of those bits (which is the case when num_bits < 16),\n               // the stream actually read past the end so it is malformed.\n               return stbi__err(\"unexpected end\",\"Corrupt PNG\");\n            }\n            return 1;\n         }\n         if (z >= 286) return stbi__err(\"bad huffman code\",\"Corrupt PNG\"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data\n         z -= 257;\n         len = stbi__zlength_base[z];\n         if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);\n         z = stbi__zhuffman_decode(a, &a->z_distance);\n         if (z < 0 || z >= 30) return stbi__err(\"bad huffman code\",\"Corrupt PNG\"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data\n         dist = stbi__zdist_base[z];\n         if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);\n         if (zout - a->zout_start < dist) return stbi__err(\"bad dist\",\"Corrupt PNG\");\n         if (len > a->zort_end - zout) {\n            if (!stbi__zexpand(a, zout, len)) return 0;\n            zout = a->zout;\n         }\n         p = (stbi_uc *) (zout - dist);\n         if (dist == 1) { // run of one byte; common in images.\n            stbi_uc v = *p;\n            if (len) { do *zout++ = v; while (--len); }\n         } else {\n            if (len) { do *zout++ = *p++; while (--len); }\n         }\n      }\n   }\n}\n\nstatic int stbi__compute_huffman_codes(stbi__zbuf *a)\n{\n   static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };\n   stbi__zhuffman z_codelength;\n   stbi_uc lencodes[286+32+137];//padding for maximum single op\n   stbi_uc codelength_sizes[19];\n   int i,n;\n\n   int hlit  = stbi__zreceive(a,5) + 257;\n   int hdist = stbi__zreceive(a,5) + 1;\n   int hclen = stbi__zreceive(a,4) + 4;\n   int ntot  = hlit + hdist;\n\n   memset(codelength_sizes, 0, sizeof(codelength_sizes));\n   for (i=0; i < hclen; ++i) {\n      int s = stbi__zreceive(a,3);\n      codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;\n   }\n   if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;\n\n   n = 0;\n   while (n < ntot) {\n      int c = stbi__zhuffman_decode(a, &z_codelength);\n      if (c < 0 || c >= 19) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n      if (c < 16)\n         lencodes[n++] = (stbi_uc) c;\n      else {\n         stbi_uc fill = 0;\n         if (c == 16) {\n            c = stbi__zreceive(a,2)+3;\n            if (n == 0) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n            fill = lencodes[n-1];\n         } else if (c == 17) {\n            c = stbi__zreceive(a,3)+3;\n         } else if (c == 18) {\n            c = stbi__zreceive(a,7)+11;\n         } else {\n            return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n         }\n         if (ntot - n < c) return stbi__err(\"bad codelengths\", \"Corrupt PNG\");\n         memset(lencodes+n, fill, c);\n         n += c;\n      }\n   }\n   if (n != ntot) return stbi__err(\"bad codelengths\",\"Corrupt PNG\");\n   if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;\n   if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;\n   return 1;\n}\n\nstatic int stbi__parse_uncompressed_block(stbi__zbuf *a)\n{\n   stbi_uc header[4];\n   int len,nlen,k;\n   if (a->num_bits & 7)\n      stbi__zreceive(a, a->num_bits & 7); // discard\n   // drain the bit-packed data into header\n   k = 0;\n   while (a->num_bits > 0) {\n      header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check\n      a->code_buffer >>= 8;\n      a->num_bits -= 8;\n   }\n   if (a->num_bits < 0) return stbi__err(\"zlib corrupt\",\"Corrupt PNG\");\n   // now fill header the normal way\n   while (k < 4)\n      header[k++] = stbi__zget8(a);\n   len  = header[1] * 256 + header[0];\n   nlen = header[3] * 256 + header[2];\n   if (nlen != (len ^ 0xffff)) return stbi__err(\"zlib corrupt\",\"Corrupt PNG\");\n   if (a->zbuffer + len > a->zbuffer_end) return stbi__err(\"read past buffer\",\"Corrupt PNG\");\n   if (a->zout + len > a->zort_end)\n      if (!stbi__zexpand(a, a->zout, len)) return 0;\n   memcpy(a->zout, a->zbuffer, len);\n   a->zbuffer += len;\n   a->zout += len;\n   return 1;\n}\n\nstatic int stbi__parse_zlib_header(stbi__zbuf *a)\n{\n   int cmf   = stbi__zget8(a);\n   int cm    = cmf & 15;\n   /* int cinfo = cmf >> 4; */\n   int flg   = stbi__zget8(a);\n   if (stbi__zeof(a)) return stbi__err(\"bad zlib header\",\"Corrupt PNG\"); // zlib spec\n   if ((cmf*256+flg) % 31 != 0) return stbi__err(\"bad zlib header\",\"Corrupt PNG\"); // zlib spec\n   if (flg & 32) return stbi__err(\"no preset dict\",\"Corrupt PNG\"); // preset dictionary not allowed in png\n   if (cm != 8) return stbi__err(\"bad compression\",\"Corrupt PNG\"); // DEFLATE required for png\n   // window = 1 << (8 + cinfo)... but who cares, we fully buffer output\n   return 1;\n}\n\nstatic const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] =\n{\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,\n   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,\n   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8\n};\nstatic const stbi_uc stbi__zdefault_distance[32] =\n{\n   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5\n};\n/*\nInit algorithm:\n{\n   int i;   // use <= to match clearly with spec\n   for (i=0; i <= 143; ++i)     stbi__zdefault_length[i]   = 8;\n   for (   ; i <= 255; ++i)     stbi__zdefault_length[i]   = 9;\n   for (   ; i <= 279; ++i)     stbi__zdefault_length[i]   = 7;\n   for (   ; i <= 287; ++i)     stbi__zdefault_length[i]   = 8;\n\n   for (i=0; i <=  31; ++i)     stbi__zdefault_distance[i] = 5;\n}\n*/\n\nstatic int stbi__parse_zlib(stbi__zbuf *a, int parse_header)\n{\n   int final, type;\n   if (parse_header)\n      if (!stbi__parse_zlib_header(a)) return 0;\n   a->num_bits = 0;\n   a->code_buffer = 0;\n   a->hit_zeof_once = 0;\n   do {\n      final = stbi__zreceive(a,1);\n      type = stbi__zreceive(a,2);\n      if (type == 0) {\n         if (!stbi__parse_uncompressed_block(a)) return 0;\n      } else if (type == 3) {\n         return 0;\n      } else {\n         if (type == 1) {\n            // use fixed code lengths\n            if (!stbi__zbuild_huffman(&a->z_length  , stbi__zdefault_length  , STBI__ZNSYMS)) return 0;\n            if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance,  32)) return 0;\n         } else {\n            if (!stbi__compute_huffman_codes(a)) return 0;\n         }\n         if (!stbi__parse_huffman_block(a)) return 0;\n      }\n   } while (!final);\n   return 1;\n}\n\nstatic int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)\n{\n   a->zout_start = obuf;\n   a->zout       = obuf;\n   a->zort_end   = obuf + olen;\n   a->z_expandable = exp;\n\n   return stbi__parse_zlib(a, parse_header);\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(initial_size);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer + len;\n   if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)\n{\n   return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);\n}\n\nSTBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(initial_size);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer + len;\n   if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)\n{\n   stbi__zbuf a;\n   a.zbuffer = (stbi_uc *) ibuffer;\n   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;\n   if (stbi__do_zlib(&a, obuffer, olen, 0, 1))\n      return (int) (a.zout - a.zout_start);\n   else\n      return -1;\n}\n\nSTBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)\n{\n   stbi__zbuf a;\n   char *p = (char *) stbi__malloc(16384);\n   if (p == NULL) return NULL;\n   a.zbuffer = (stbi_uc *) buffer;\n   a.zbuffer_end = (stbi_uc *) buffer+len;\n   if (stbi__do_zlib(&a, p, 16384, 1, 0)) {\n      if (outlen) *outlen = (int) (a.zout - a.zout_start);\n      return a.zout_start;\n   } else {\n      STBI_FREE(a.zout_start);\n      return NULL;\n   }\n}\n\nSTBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)\n{\n   stbi__zbuf a;\n   a.zbuffer = (stbi_uc *) ibuffer;\n   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;\n   if (stbi__do_zlib(&a, obuffer, olen, 0, 0))\n      return (int) (a.zout - a.zout_start);\n   else\n      return -1;\n}\n#endif\n\n// public domain \"baseline\" PNG decoder   v0.10  Sean Barrett 2006-11-18\n//    simple implementation\n//      - only 8-bit samples\n//      - no CRC checking\n//      - allocates lots of intermediate memory\n//        - avoids problem of streaming data between subsystems\n//        - avoids explicit window management\n//    performance\n//      - uses stb_zlib, a PD zlib implementation with fast huffman decoding\n\n#ifndef STBI_NO_PNG\ntypedef struct\n{\n   stbi__uint32 length;\n   stbi__uint32 type;\n} stbi__pngchunk;\n\nstatic stbi__pngchunk stbi__get_chunk_header(stbi__context *s)\n{\n   stbi__pngchunk c;\n   c.length = stbi__get32be(s);\n   c.type   = stbi__get32be(s);\n   return c;\n}\n\nstatic int stbi__check_png_header(stbi__context *s)\n{\n   static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };\n   int i;\n   for (i=0; i < 8; ++i)\n      if (stbi__get8(s) != png_sig[i]) return stbi__err(\"bad png sig\",\"Not a PNG\");\n   return 1;\n}\n\ntypedef struct\n{\n   stbi__context *s;\n   stbi_uc *idata, *expanded, *out;\n   int depth;\n} stbi__png;\n\n\nenum {\n   STBI__F_none=0,\n   STBI__F_sub=1,\n   STBI__F_up=2,\n   STBI__F_avg=3,\n   STBI__F_paeth=4,\n   // synthetic filter used for first scanline to avoid needing a dummy row of 0s\n   STBI__F_avg_first\n};\n\nstatic stbi_uc first_row_filter[5] =\n{\n   STBI__F_none,\n   STBI__F_sub,\n   STBI__F_none,\n   STBI__F_avg_first,\n   STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub\n};\n\nstatic int stbi__paeth(int a, int b, int c)\n{\n   // This formulation looks very different from the reference in the PNG spec, but is\n   // actually equivalent and has favorable data dependencies and admits straightforward\n   // generation of branch-free code, which helps performance significantly.\n   int thresh = c*3 - (a + b);\n   int lo = a < b ? a : b;\n   int hi = a < b ? b : a;\n   int t0 = (hi <= thresh) ? lo : c;\n   int t1 = (thresh <= lo) ? hi : t0;\n   return t1;\n}\n\nstatic const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };\n\n// adds an extra all-255 alpha channel\n// dest == src is legal\n// img_n must be 1 or 3\nstatic void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n)\n{\n   int i;\n   // must process data backwards since we allow dest==src\n   if (img_n == 1) {\n      for (i=x-1; i >= 0; --i) {\n         dest[i*2+1] = 255;\n         dest[i*2+0] = src[i];\n      }\n   } else {\n      STBI_ASSERT(img_n == 3);\n      for (i=x-1; i >= 0; --i) {\n         dest[i*4+3] = 255;\n         dest[i*4+2] = src[i*3+2];\n         dest[i*4+1] = src[i*3+1];\n         dest[i*4+0] = src[i*3+0];\n      }\n   }\n}\n\n// create the png data from post-deflated data\nstatic int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)\n{\n   int bytes = (depth == 16 ? 2 : 1);\n   stbi__context *s = a->s;\n   stbi__uint32 i,j,stride = x*out_n*bytes;\n   stbi__uint32 img_len, img_width_bytes;\n   stbi_uc *filter_buf;\n   int all_ok = 1;\n   int k;\n   int img_n = s->img_n; // copy it into a local for later\n\n   int output_bytes = out_n*bytes;\n   int filter_bytes = img_n*bytes;\n   int width = x;\n\n   STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);\n   a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into\n   if (!a->out) return stbi__err(\"outofmem\", \"Out of memory\");\n\n   // note: error exits here don't need to clean up a->out individually,\n   // stbi__do_png always does on error.\n   if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err(\"too large\", \"Corrupt PNG\");\n   img_width_bytes = (((img_n * x * depth) + 7) >> 3);\n   if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err(\"too large\", \"Corrupt PNG\");\n   img_len = (img_width_bytes + 1) * y;\n\n   // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,\n   // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),\n   // so just check for raw_len < img_len always.\n   if (raw_len < img_len) return stbi__err(\"not enough pixels\",\"Corrupt PNG\");\n\n   // Allocate two scan lines worth of filter workspace buffer.\n   filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0);\n   if (!filter_buf) return stbi__err(\"outofmem\", \"Out of memory\");\n\n   // Filtering for low-bit-depth images\n   if (depth < 8) {\n      filter_bytes = 1;\n      width = img_width_bytes;\n   }\n\n   for (j=0; j < y; ++j) {\n      // cur/prior filter buffers alternate\n      stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes;\n      stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes;\n      stbi_uc *dest = a->out + stride*j;\n      int nk = width * filter_bytes;\n      int filter = *raw++;\n\n      // check filter type\n      if (filter > 4) {\n         all_ok = stbi__err(\"invalid filter\",\"Corrupt PNG\");\n         break;\n      }\n\n      // if first row, use special filter that doesn't sample previous row\n      if (j == 0) filter = first_row_filter[filter];\n\n      // perform actual filtering\n      switch (filter) {\n      case STBI__F_none:\n         memcpy(cur, raw, nk);\n         break;\n      case STBI__F_sub:\n         memcpy(cur, raw, filter_bytes);\n         for (k = filter_bytes; k < nk; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);\n         break;\n      case STBI__F_up:\n         for (k = 0; k < nk; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + prior[k]);\n         break;\n      case STBI__F_avg:\n         for (k = 0; k < filter_bytes; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1));\n         for (k = filter_bytes; k < nk; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1));\n         break;\n      case STBI__F_paeth:\n         for (k = 0; k < filter_bytes; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)\n         for (k = filter_bytes; k < nk; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes]));\n         break;\n      case STBI__F_avg_first:\n         memcpy(cur, raw, filter_bytes);\n         for (k = filter_bytes; k < nk; ++k)\n            cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1));\n         break;\n      }\n\n      raw += nk;\n\n      // expand decoded bits in cur to dest, also adding an extra alpha channel if desired\n      if (depth < 8) {\n         stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale gs values to 0..255 range\n         stbi_uc *in = cur;\n         stbi_uc *out = dest;\n         stbi_uc inb = 0;\n         stbi__uint32 nsmp = x*img_n;\n\n         // expand bits to bytes first\n         if (depth == 4) {\n            for (i=0; i < nsmp; ++i) {\n               if ((i & 1) == 0) inb = *in++;\n               *out++ = scale * (inb >> 4);\n               inb <<= 4;\n            }\n         } else if (depth == 2) {\n            for (i=0; i < nsmp; ++i) {\n               if ((i & 3) == 0) inb = *in++;\n               *out++ = scale * (inb >> 6);\n               inb <<= 2;\n            }\n         } else {\n            STBI_ASSERT(depth == 1);\n            for (i=0; i < nsmp; ++i) {\n               if ((i & 7) == 0) inb = *in++;\n               *out++ = scale * (inb >> 7);\n               inb <<= 1;\n            }\n         }\n\n         // insert alpha=255 values if desired\n         if (img_n != out_n)\n            stbi__create_png_alpha_expand8(dest, dest, x, img_n);\n      } else if (depth == 8) {\n         if (img_n == out_n)\n            memcpy(dest, cur, x*img_n);\n         else\n            stbi__create_png_alpha_expand8(dest, cur, x, img_n);\n      } else if (depth == 16) {\n         // convert the image data from big-endian to platform-native\n         stbi__uint16 *dest16 = (stbi__uint16*)dest;\n         stbi__uint32 nsmp = x*img_n;\n\n         if (img_n == out_n) {\n            for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)\n               *dest16 = (cur[0] << 8) | cur[1];\n         } else {\n            STBI_ASSERT(img_n+1 == out_n);\n            if (img_n == 1) {\n               for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {\n                  dest16[0] = (cur[0] << 8) | cur[1];\n                  dest16[1] = 0xffff;\n               }\n            } else {\n               STBI_ASSERT(img_n == 3);\n               for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {\n                  dest16[0] = (cur[0] << 8) | cur[1];\n                  dest16[1] = (cur[2] << 8) | cur[3];\n                  dest16[2] = (cur[4] << 8) | cur[5];\n                  dest16[3] = 0xffff;\n               }\n            }\n         }\n      }\n   }\n\n   STBI_FREE(filter_buf);\n   if (!all_ok) return 0;\n\n   return 1;\n}\n\nstatic int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)\n{\n   int bytes = (depth == 16 ? 2 : 1);\n   int out_bytes = out_n * bytes;\n   stbi_uc *final;\n   int p;\n   if (!interlaced)\n      return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);\n\n   // de-interlacing\n   final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);\n   if (!final) return stbi__err(\"outofmem\", \"Out of memory\");\n   for (p=0; p < 7; ++p) {\n      int xorig[] = { 0,4,0,2,0,1,0 };\n      int yorig[] = { 0,0,4,0,2,0,1 };\n      int xspc[]  = { 8,8,4,4,2,2,1 };\n      int yspc[]  = { 8,8,8,4,4,2,2 };\n      int i,j,x,y;\n      // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1\n      x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];\n      y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];\n      if (x && y) {\n         stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;\n         if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {\n            STBI_FREE(final);\n            return 0;\n         }\n         for (j=0; j < y; ++j) {\n            for (i=0; i < x; ++i) {\n               int out_y = j*yspc[p]+yorig[p];\n               int out_x = i*xspc[p]+xorig[p];\n               memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,\n                      a->out + (j*x+i)*out_bytes, out_bytes);\n            }\n         }\n         STBI_FREE(a->out);\n         image_data += img_len;\n         image_data_len -= img_len;\n      }\n   }\n   a->out = final;\n\n   return 1;\n}\n\nstatic int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi_uc *p = z->out;\n\n   // compute color-based transparency, assuming we've\n   // already got 255 as the alpha value in the output\n   STBI_ASSERT(out_n == 2 || out_n == 4);\n\n   if (out_n == 2) {\n      for (i=0; i < pixel_count; ++i) {\n         p[1] = (p[0] == tc[0] ? 0 : 255);\n         p += 2;\n      }\n   } else {\n      for (i=0; i < pixel_count; ++i) {\n         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])\n            p[3] = 0;\n         p += 4;\n      }\n   }\n   return 1;\n}\n\nstatic int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi__uint16 *p = (stbi__uint16*) z->out;\n\n   // compute color-based transparency, assuming we've\n   // already got 65535 as the alpha value in the output\n   STBI_ASSERT(out_n == 2 || out_n == 4);\n\n   if (out_n == 2) {\n      for (i = 0; i < pixel_count; ++i) {\n         p[1] = (p[0] == tc[0] ? 0 : 65535);\n         p += 2;\n      }\n   } else {\n      for (i = 0; i < pixel_count; ++i) {\n         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])\n            p[3] = 0;\n         p += 4;\n      }\n   }\n   return 1;\n}\n\nstatic int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)\n{\n   stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;\n   stbi_uc *p, *temp_out, *orig = a->out;\n\n   p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);\n   if (p == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n\n   // between here and free(out) below, exitting would leak\n   temp_out = p;\n\n   if (pal_img_n == 3) {\n      for (i=0; i < pixel_count; ++i) {\n         int n = orig[i]*4;\n         p[0] = palette[n  ];\n         p[1] = palette[n+1];\n         p[2] = palette[n+2];\n         p += 3;\n      }\n   } else {\n      for (i=0; i < pixel_count; ++i) {\n         int n = orig[i]*4;\n         p[0] = palette[n  ];\n         p[1] = palette[n+1];\n         p[2] = palette[n+2];\n         p[3] = palette[n+3];\n         p += 4;\n      }\n   }\n   STBI_FREE(a->out);\n   a->out = temp_out;\n\n   STBI_NOTUSED(len);\n\n   return 1;\n}\n\nstatic int stbi__unpremultiply_on_load_global = 0;\nstatic int stbi__de_iphone_flag_global = 0;\n\nSTBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)\n{\n   stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply;\n}\n\nSTBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)\n{\n   stbi__de_iphone_flag_global = flag_true_if_should_convert;\n}\n\n#ifndef STBI_THREAD_LOCAL\n#define stbi__unpremultiply_on_load  stbi__unpremultiply_on_load_global\n#define stbi__de_iphone_flag  stbi__de_iphone_flag_global\n#else\nstatic STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;\nstatic STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;\n\nSTBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)\n{\n   stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;\n   stbi__unpremultiply_on_load_set = 1;\n}\n\nSTBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert)\n{\n   stbi__de_iphone_flag_local = flag_true_if_should_convert;\n   stbi__de_iphone_flag_set = 1;\n}\n\n#define stbi__unpremultiply_on_load  (stbi__unpremultiply_on_load_set           \\\n                                       ? stbi__unpremultiply_on_load_local      \\\n                                       : stbi__unpremultiply_on_load_global)\n#define stbi__de_iphone_flag  (stbi__de_iphone_flag_set                         \\\n                                ? stbi__de_iphone_flag_local                    \\\n                                : stbi__de_iphone_flag_global)\n#endif // STBI_THREAD_LOCAL\n\nstatic void stbi__de_iphone(stbi__png *z)\n{\n   stbi__context *s = z->s;\n   stbi__uint32 i, pixel_count = s->img_x * s->img_y;\n   stbi_uc *p = z->out;\n\n   if (s->img_out_n == 3) {  // convert bgr to rgb\n      for (i=0; i < pixel_count; ++i) {\n         stbi_uc t = p[0];\n         p[0] = p[2];\n         p[2] = t;\n         p += 3;\n      }\n   } else {\n      STBI_ASSERT(s->img_out_n == 4);\n      if (stbi__unpremultiply_on_load) {\n         // convert bgr to rgb and unpremultiply\n         for (i=0; i < pixel_count; ++i) {\n            stbi_uc a = p[3];\n            stbi_uc t = p[0];\n            if (a) {\n               stbi_uc half = a / 2;\n               p[0] = (p[2] * 255 + half) / a;\n               p[1] = (p[1] * 255 + half) / a;\n               p[2] = ( t   * 255 + half) / a;\n            } else {\n               p[0] = p[2];\n               p[2] = t;\n            }\n            p += 4;\n         }\n      } else {\n         // convert bgr to rgb\n         for (i=0; i < pixel_count; ++i) {\n            stbi_uc t = p[0];\n            p[0] = p[2];\n            p[2] = t;\n            p += 4;\n         }\n      }\n   }\n}\n\n#define STBI__PNG_TYPE(a,b,c,d)  (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))\n\nstatic int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)\n{\n   stbi_uc palette[1024], pal_img_n=0;\n   stbi_uc has_trans=0, tc[3]={0};\n   stbi__uint16 tc16[3];\n   stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;\n   int first=1,k,interlace=0, color=0, is_iphone=0;\n   stbi__context *s = z->s;\n\n   z->expanded = NULL;\n   z->idata = NULL;\n   z->out = NULL;\n\n   if (!stbi__check_png_header(s)) return 0;\n\n   if (scan == STBI__SCAN_type) return 1;\n\n   for (;;) {\n      stbi__pngchunk c = stbi__get_chunk_header(s);\n      switch (c.type) {\n         case STBI__PNG_TYPE('C','g','B','I'):\n            is_iphone = 1;\n            stbi__skip(s, c.length);\n            break;\n         case STBI__PNG_TYPE('I','H','D','R'): {\n            int comp,filter;\n            if (!first) return stbi__err(\"multiple IHDR\",\"Corrupt PNG\");\n            first = 0;\n            if (c.length != 13) return stbi__err(\"bad IHDR len\",\"Corrupt PNG\");\n            s->img_x = stbi__get32be(s);\n            s->img_y = stbi__get32be(s);\n            if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n            if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n            z->depth = stbi__get8(s);  if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16)  return stbi__err(\"1/2/4/8/16-bit only\",\"PNG not supported: 1/2/4/8/16-bit only\");\n            color = stbi__get8(s);  if (color > 6)         return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            if (color == 3 && z->depth == 16)                  return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err(\"bad ctype\",\"Corrupt PNG\");\n            comp  = stbi__get8(s);  if (comp) return stbi__err(\"bad comp method\",\"Corrupt PNG\");\n            filter= stbi__get8(s);  if (filter) return stbi__err(\"bad filter method\",\"Corrupt PNG\");\n            interlace = stbi__get8(s); if (interlace>1) return stbi__err(\"bad interlace method\",\"Corrupt PNG\");\n            if (!s->img_x || !s->img_y) return stbi__err(\"0-pixel image\",\"Corrupt PNG\");\n            if (!pal_img_n) {\n               s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);\n               if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err(\"too large\", \"Image too large to decode\");\n            } else {\n               // if paletted, then pal_n is our final components, and\n               // img_n is # components to decompress/filter.\n               s->img_n = 1;\n               if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err(\"too large\",\"Corrupt PNG\");\n            }\n            // even with SCAN_header, have to scan to see if we have a tRNS\n            break;\n         }\n\n         case STBI__PNG_TYPE('P','L','T','E'):  {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (c.length > 256*3) return stbi__err(\"invalid PLTE\",\"Corrupt PNG\");\n            pal_len = c.length / 3;\n            if (pal_len * 3 != c.length) return stbi__err(\"invalid PLTE\",\"Corrupt PNG\");\n            for (i=0; i < pal_len; ++i) {\n               palette[i*4+0] = stbi__get8(s);\n               palette[i*4+1] = stbi__get8(s);\n               palette[i*4+2] = stbi__get8(s);\n               palette[i*4+3] = 255;\n            }\n            break;\n         }\n\n         case STBI__PNG_TYPE('t','R','N','S'): {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (z->idata) return stbi__err(\"tRNS after IDAT\",\"Corrupt PNG\");\n            if (pal_img_n) {\n               if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }\n               if (pal_len == 0) return stbi__err(\"tRNS before PLTE\",\"Corrupt PNG\");\n               if (c.length > pal_len) return stbi__err(\"bad tRNS len\",\"Corrupt PNG\");\n               pal_img_n = 4;\n               for (i=0; i < c.length; ++i)\n                  palette[i*4+3] = stbi__get8(s);\n            } else {\n               if (!(s->img_n & 1)) return stbi__err(\"tRNS with alpha\",\"Corrupt PNG\");\n               if (c.length != (stbi__uint32) s->img_n*2) return stbi__err(\"bad tRNS len\",\"Corrupt PNG\");\n               has_trans = 1;\n               // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.\n               if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }\n               if (z->depth == 16) {\n                  for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is\n               } else {\n                  for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger\n               }\n            }\n            break;\n         }\n\n         case STBI__PNG_TYPE('I','D','A','T'): {\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (pal_img_n && !pal_len) return stbi__err(\"no PLTE\",\"Corrupt PNG\");\n            if (scan == STBI__SCAN_header) {\n               // header scan definitely stops at first IDAT\n               if (pal_img_n)\n                  s->img_n = pal_img_n;\n               return 1;\n            }\n            if (c.length > (1u << 30)) return stbi__err(\"IDAT size limit\", \"IDAT section larger than 2^30 bytes\");\n            if ((int)(ioff + c.length) < (int)ioff) return 0;\n            if (ioff + c.length > idata_limit) {\n               stbi__uint32 idata_limit_old = idata_limit;\n               stbi_uc *p;\n               if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;\n               while (ioff + c.length > idata_limit)\n                  idata_limit *= 2;\n               STBI_NOTUSED(idata_limit_old);\n               p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err(\"outofmem\", \"Out of memory\");\n               z->idata = p;\n            }\n            if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err(\"outofdata\",\"Corrupt PNG\");\n            ioff += c.length;\n            break;\n         }\n\n         case STBI__PNG_TYPE('I','E','N','D'): {\n            stbi__uint32 raw_len, bpl;\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if (scan != STBI__SCAN_load) return 1;\n            if (z->idata == NULL) return stbi__err(\"no IDAT\",\"Corrupt PNG\");\n            // initial guess for decoded data size to avoid unnecessary reallocs\n            bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component\n            raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;\n            z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);\n            if (z->expanded == NULL) return 0; // zlib should set error\n            STBI_FREE(z->idata); z->idata = NULL;\n            if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)\n               s->img_out_n = s->img_n+1;\n            else\n               s->img_out_n = s->img_n;\n            if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;\n            if (has_trans) {\n               if (z->depth == 16) {\n                  if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;\n               } else {\n                  if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;\n               }\n            }\n            if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)\n               stbi__de_iphone(z);\n            if (pal_img_n) {\n               // pal_img_n == 3 or 4\n               s->img_n = pal_img_n; // record the actual colors we had\n               s->img_out_n = pal_img_n;\n               if (req_comp >= 3) s->img_out_n = req_comp;\n               if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))\n                  return 0;\n            } else if (has_trans) {\n               // non-paletted image with tRNS -> source image has (constant) alpha\n               ++s->img_n;\n            }\n            STBI_FREE(z->expanded); z->expanded = NULL;\n            // end of PNG chunk, read and skip CRC\n            stbi__get32be(s);\n            return 1;\n         }\n\n         default:\n            // if critical, fail\n            if (first) return stbi__err(\"first not IHDR\", \"Corrupt PNG\");\n            if ((c.type & (1 << 29)) == 0) {\n               #ifndef STBI_NO_FAILURE_STRINGS\n               // not threadsafe\n               static char invalid_chunk[] = \"XXXX PNG chunk not known\";\n               invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);\n               invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);\n               invalid_chunk[2] = STBI__BYTECAST(c.type >>  8);\n               invalid_chunk[3] = STBI__BYTECAST(c.type >>  0);\n               #endif\n               return stbi__err(invalid_chunk, \"PNG not supported: unknown PNG chunk type\");\n            }\n            stbi__skip(s, c.length);\n            break;\n      }\n      // end of PNG chunk, read and skip CRC\n      stbi__get32be(s);\n   }\n}\n\nstatic void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)\n{\n   void *result=NULL;\n   if (req_comp < 0 || req_comp > 4) return stbi__errpuc(\"bad req_comp\", \"Internal error\");\n   if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {\n      if (p->depth <= 8)\n         ri->bits_per_channel = 8;\n      else if (p->depth == 16)\n         ri->bits_per_channel = 16;\n      else\n         return stbi__errpuc(\"bad bits_per_channel\", \"PNG not supported: unsupported color depth\");\n      result = p->out;\n      p->out = NULL;\n      if (req_comp && req_comp != p->s->img_out_n) {\n         if (ri->bits_per_channel == 8)\n            result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);\n         else\n            result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);\n         p->s->img_out_n = req_comp;\n         if (result == NULL) return result;\n      }\n      *x = p->s->img_x;\n      *y = p->s->img_y;\n      if (n) *n = p->s->img_n;\n   }\n   STBI_FREE(p->out);      p->out      = NULL;\n   STBI_FREE(p->expanded); p->expanded = NULL;\n   STBI_FREE(p->idata);    p->idata    = NULL;\n\n   return result;\n}\n\nstatic void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi__png p;\n   p.s = s;\n   return stbi__do_png(&p, x,y,comp,req_comp, ri);\n}\n\nstatic int stbi__png_test(stbi__context *s)\n{\n   int r;\n   r = stbi__check_png_header(s);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)\n{\n   if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {\n      stbi__rewind( p->s );\n      return 0;\n   }\n   if (x) *x = p->s->img_x;\n   if (y) *y = p->s->img_y;\n   if (comp) *comp = p->s->img_n;\n   return 1;\n}\n\nstatic int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   stbi__png p;\n   p.s = s;\n   return stbi__png_info_raw(&p, x, y, comp);\n}\n\nstatic int stbi__png_is16(stbi__context *s)\n{\n   stbi__png p;\n   p.s = s;\n   if (!stbi__png_info_raw(&p, NULL, NULL, NULL))\n\t   return 0;\n   if (p.depth != 16) {\n      stbi__rewind(p.s);\n      return 0;\n   }\n   return 1;\n}\n#endif\n\n// Microsoft/Windows BMP image\n\n#ifndef STBI_NO_BMP\nstatic int stbi__bmp_test_raw(stbi__context *s)\n{\n   int r;\n   int sz;\n   if (stbi__get8(s) != 'B') return 0;\n   if (stbi__get8(s) != 'M') return 0;\n   stbi__get32le(s); // discard filesize\n   stbi__get16le(s); // discard reserved\n   stbi__get16le(s); // discard reserved\n   stbi__get32le(s); // discard data offset\n   sz = stbi__get32le(s);\n   r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);\n   return r;\n}\n\nstatic int stbi__bmp_test(stbi__context *s)\n{\n   int r = stbi__bmp_test_raw(s);\n   stbi__rewind(s);\n   return r;\n}\n\n\n// returns 0..31 for the highest set bit\nstatic int stbi__high_bit(unsigned int z)\n{\n   int n=0;\n   if (z == 0) return -1;\n   if (z >= 0x10000) { n += 16; z >>= 16; }\n   if (z >= 0x00100) { n +=  8; z >>=  8; }\n   if (z >= 0x00010) { n +=  4; z >>=  4; }\n   if (z >= 0x00004) { n +=  2; z >>=  2; }\n   if (z >= 0x00002) { n +=  1;/* >>=  1;*/ }\n   return n;\n}\n\nstatic int stbi__bitcount(unsigned int a)\n{\n   a = (a & 0x55555555) + ((a >>  1) & 0x55555555); // max 2\n   a = (a & 0x33333333) + ((a >>  2) & 0x33333333); // max 4\n   a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits\n   a = (a + (a >> 8)); // max 16 per 8 bits\n   a = (a + (a >> 16)); // max 32 per 8 bits\n   return a & 0xff;\n}\n\n// extract an arbitrarily-aligned N-bit value (N=bits)\n// from v, and then make it 8-bits long and fractionally\n// extend it to full full range.\nstatic int stbi__shiftsigned(unsigned int v, int shift, int bits)\n{\n   static unsigned int mul_table[9] = {\n      0,\n      0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,\n      0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,\n   };\n   static unsigned int shift_table[9] = {\n      0, 0,0,1,0,2,4,6,0,\n   };\n   if (shift < 0)\n      v <<= -shift;\n   else\n      v >>= shift;\n   STBI_ASSERT(v < 256);\n   v >>= (8-bits);\n   STBI_ASSERT(bits >= 0 && bits <= 8);\n   return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];\n}\n\ntypedef struct\n{\n   int bpp, offset, hsz;\n   unsigned int mr,mg,mb,ma, all_a;\n   int extra_read;\n} stbi__bmp_data;\n\nstatic int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress)\n{\n   // BI_BITFIELDS specifies masks explicitly, don't override\n   if (compress == 3)\n      return 1;\n\n   if (compress == 0) {\n      if (info->bpp == 16) {\n         info->mr = 31u << 10;\n         info->mg = 31u <<  5;\n         info->mb = 31u <<  0;\n      } else if (info->bpp == 32) {\n         info->mr = 0xffu << 16;\n         info->mg = 0xffu <<  8;\n         info->mb = 0xffu <<  0;\n         info->ma = 0xffu << 24;\n         info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0\n      } else {\n         // otherwise, use defaults, which is all-0\n         info->mr = info->mg = info->mb = info->ma = 0;\n      }\n      return 1;\n   }\n   return 0; // error\n}\n\nstatic void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)\n{\n   int hsz;\n   if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc(\"not BMP\", \"Corrupt BMP\");\n   stbi__get32le(s); // discard filesize\n   stbi__get16le(s); // discard reserved\n   stbi__get16le(s); // discard reserved\n   info->offset = stbi__get32le(s);\n   info->hsz = hsz = stbi__get32le(s);\n   info->mr = info->mg = info->mb = info->ma = 0;\n   info->extra_read = 14;\n\n   if (info->offset < 0) return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n\n   if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc(\"unknown BMP\", \"BMP type not supported: unknown\");\n   if (hsz == 12) {\n      s->img_x = stbi__get16le(s);\n      s->img_y = stbi__get16le(s);\n   } else {\n      s->img_x = stbi__get32le(s);\n      s->img_y = stbi__get32le(s);\n   }\n   if (stbi__get16le(s) != 1) return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n   info->bpp = stbi__get16le(s);\n   if (hsz != 12) {\n      int compress = stbi__get32le(s);\n      if (compress == 1 || compress == 2) return stbi__errpuc(\"BMP RLE\", \"BMP type not supported: RLE\");\n      if (compress >= 4) return stbi__errpuc(\"BMP JPEG/PNG\", \"BMP type not supported: unsupported compression\"); // this includes PNG/JPEG modes\n      if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc(\"bad BMP\", \"bad BMP\"); // bitfields requires 16 or 32 bits/pixel\n      stbi__get32le(s); // discard sizeof\n      stbi__get32le(s); // discard hres\n      stbi__get32le(s); // discard vres\n      stbi__get32le(s); // discard colorsused\n      stbi__get32le(s); // discard max important\n      if (hsz == 40 || hsz == 56) {\n         if (hsz == 56) {\n            stbi__get32le(s);\n            stbi__get32le(s);\n            stbi__get32le(s);\n            stbi__get32le(s);\n         }\n         if (info->bpp == 16 || info->bpp == 32) {\n            if (compress == 0) {\n               stbi__bmp_set_mask_defaults(info, compress);\n            } else if (compress == 3) {\n               info->mr = stbi__get32le(s);\n               info->mg = stbi__get32le(s);\n               info->mb = stbi__get32le(s);\n               info->extra_read += 12;\n               // not documented, but generated by photoshop and handled by mspaint\n               if (info->mr == info->mg && info->mg == info->mb) {\n                  // ?!?!?\n                  return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n               }\n            } else\n               return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n         }\n      } else {\n         // V4/V5 header\n         int i;\n         if (hsz != 108 && hsz != 124)\n            return stbi__errpuc(\"bad BMP\", \"bad BMP\");\n         info->mr = stbi__get32le(s);\n         info->mg = stbi__get32le(s);\n         info->mb = stbi__get32le(s);\n         info->ma = stbi__get32le(s);\n         if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs\n            stbi__bmp_set_mask_defaults(info, compress);\n         stbi__get32le(s); // discard color space\n         for (i=0; i < 12; ++i)\n            stbi__get32le(s); // discard color space parameters\n         if (hsz == 124) {\n            stbi__get32le(s); // discard rendering intent\n            stbi__get32le(s); // discard offset of profile data\n            stbi__get32le(s); // discard size of profile data\n            stbi__get32le(s); // discard reserved\n         }\n      }\n   }\n   return (void *) 1;\n}\n\n\nstatic void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *out;\n   unsigned int mr=0,mg=0,mb=0,ma=0, all_a;\n   stbi_uc pal[256][4];\n   int psize=0,i,j,width;\n   int flip_vertically, pad, target;\n   stbi__bmp_data info;\n   STBI_NOTUSED(ri);\n\n   info.all_a = 255;\n   if (stbi__bmp_parse_header(s, &info) == NULL)\n      return NULL; // error code already set\n\n   flip_vertically = ((int) s->img_y) > 0;\n   s->img_y = abs((int) s->img_y);\n\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   mr = info.mr;\n   mg = info.mg;\n   mb = info.mb;\n   ma = info.ma;\n   all_a = info.all_a;\n\n   if (info.hsz == 12) {\n      if (info.bpp < 24)\n         psize = (info.offset - info.extra_read - 24) / 3;\n   } else {\n      if (info.bpp < 16)\n         psize = (info.offset - info.extra_read - info.hsz) >> 2;\n   }\n   if (psize == 0) {\n      // accept some number of extra bytes after the header, but if the offset points either to before\n      // the header ends or implies a large amount of extra data, reject the file as malformed\n      int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);\n      int header_limit = 1024; // max we actually read is below 256 bytes currently.\n      int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.\n      if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {\n         return stbi__errpuc(\"bad header\", \"Corrupt BMP\");\n      }\n      // we established that bytes_read_so_far is positive and sensible.\n      // the first half of this test rejects offsets that are either too small positives, or\n      // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn\n      // ensures the number computed in the second half of the test can't overflow.\n      if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {\n         return stbi__errpuc(\"bad offset\", \"Corrupt BMP\");\n      } else {\n         stbi__skip(s, info.offset - bytes_read_so_far);\n      }\n   }\n\n   if (info.bpp == 24 && ma == 0xff000000)\n      s->img_n = 3;\n   else\n      s->img_n = ma ? 4 : 3;\n   if (req_comp && req_comp >= 3) // we can directly decode 3 or 4\n      target = req_comp;\n   else\n      target = s->img_n; // if they want monochrome, we'll post-convert\n\n   // sanity-check size\n   if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt BMP\");\n\n   out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   if (info.bpp < 16) {\n      int z=0;\n      if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc(\"invalid\", \"Corrupt BMP\"); }\n      for (i=0; i < psize; ++i) {\n         pal[i][2] = stbi__get8(s);\n         pal[i][1] = stbi__get8(s);\n         pal[i][0] = stbi__get8(s);\n         if (info.hsz != 12) stbi__get8(s);\n         pal[i][3] = 255;\n      }\n      stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));\n      if (info.bpp == 1) width = (s->img_x + 7) >> 3;\n      else if (info.bpp == 4) width = (s->img_x + 1) >> 1;\n      else if (info.bpp == 8) width = s->img_x;\n      else { STBI_FREE(out); return stbi__errpuc(\"bad bpp\", \"Corrupt BMP\"); }\n      pad = (-width)&3;\n      if (info.bpp == 1) {\n         for (j=0; j < (int) s->img_y; ++j) {\n            int bit_offset = 7, v = stbi__get8(s);\n            for (i=0; i < (int) s->img_x; ++i) {\n               int color = (v>>bit_offset)&0x1;\n               out[z++] = pal[color][0];\n               out[z++] = pal[color][1];\n               out[z++] = pal[color][2];\n               if (target == 4) out[z++] = 255;\n               if (i+1 == (int) s->img_x) break;\n               if((--bit_offset) < 0) {\n                  bit_offset = 7;\n                  v = stbi__get8(s);\n               }\n            }\n            stbi__skip(s, pad);\n         }\n      } else {\n         for (j=0; j < (int) s->img_y; ++j) {\n            for (i=0; i < (int) s->img_x; i += 2) {\n               int v=stbi__get8(s),v2=0;\n               if (info.bpp == 4) {\n                  v2 = v & 15;\n                  v >>= 4;\n               }\n               out[z++] = pal[v][0];\n               out[z++] = pal[v][1];\n               out[z++] = pal[v][2];\n               if (target == 4) out[z++] = 255;\n               if (i+1 == (int) s->img_x) break;\n               v = (info.bpp == 8) ? stbi__get8(s) : v2;\n               out[z++] = pal[v][0];\n               out[z++] = pal[v][1];\n               out[z++] = pal[v][2];\n               if (target == 4) out[z++] = 255;\n            }\n            stbi__skip(s, pad);\n         }\n      }\n   } else {\n      int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;\n      int z = 0;\n      int easy=0;\n      stbi__skip(s, info.offset - info.extra_read - info.hsz);\n      if (info.bpp == 24) width = 3 * s->img_x;\n      else if (info.bpp == 16) width = 2*s->img_x;\n      else /* bpp = 32 and pad = 0 */ width=0;\n      pad = (-width) & 3;\n      if (info.bpp == 24) {\n         easy = 1;\n      } else if (info.bpp == 32) {\n         if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)\n            easy = 2;\n      }\n      if (!easy) {\n         if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc(\"bad masks\", \"Corrupt BMP\"); }\n         // right shift amt to put high bit in position #7\n         rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);\n         gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);\n         bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);\n         ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);\n         if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc(\"bad masks\", \"Corrupt BMP\"); }\n      }\n      for (j=0; j < (int) s->img_y; ++j) {\n         if (easy) {\n            for (i=0; i < (int) s->img_x; ++i) {\n               unsigned char a;\n               out[z+2] = stbi__get8(s);\n               out[z+1] = stbi__get8(s);\n               out[z+0] = stbi__get8(s);\n               z += 3;\n               a = (easy == 2 ? stbi__get8(s) : 255);\n               all_a |= a;\n               if (target == 4) out[z++] = a;\n            }\n         } else {\n            int bpp = info.bpp;\n            for (i=0; i < (int) s->img_x; ++i) {\n               stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));\n               unsigned int a;\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));\n               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));\n               a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);\n               all_a |= a;\n               if (target == 4) out[z++] = STBI__BYTECAST(a);\n            }\n         }\n         stbi__skip(s, pad);\n      }\n   }\n\n   // if alpha channel is all 0s, replace with all 255s\n   if (target == 4 && all_a == 0)\n      for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)\n         out[i] = 255;\n\n   if (flip_vertically) {\n      stbi_uc t;\n      for (j=0; j < (int) s->img_y>>1; ++j) {\n         stbi_uc *p1 = out +      j     *s->img_x*target;\n         stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;\n         for (i=0; i < (int) s->img_x*target; ++i) {\n            t = p1[i]; p1[i] = p2[i]; p2[i] = t;\n         }\n      }\n   }\n\n   if (req_comp && req_comp != target) {\n      out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n\n   *x = s->img_x;\n   *y = s->img_y;\n   if (comp) *comp = s->img_n;\n   return out;\n}\n#endif\n\n// Targa Truevision - TGA\n// by Jonathan Dummer\n#ifndef STBI_NO_TGA\n// returns STBI_rgb or whatever, 0 on error\nstatic int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)\n{\n   // only RGB or RGBA (incl. 16bit) or grey allowed\n   if (is_rgb16) *is_rgb16 = 0;\n   switch(bits_per_pixel) {\n      case 8:  return STBI_grey;\n      case 16: if(is_grey) return STBI_grey_alpha;\n               // fallthrough\n      case 15: if(is_rgb16) *is_rgb16 = 1;\n               return STBI_rgb;\n      case 24: // fallthrough\n      case 32: return bits_per_pixel/8;\n      default: return 0;\n   }\n}\n\nstatic int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)\n{\n    int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;\n    int sz, tga_colormap_type;\n    stbi__get8(s);                   // discard Offset\n    tga_colormap_type = stbi__get8(s); // colormap type\n    if( tga_colormap_type > 1 ) {\n        stbi__rewind(s);\n        return 0;      // only RGB or indexed allowed\n    }\n    tga_image_type = stbi__get8(s); // image type\n    if ( tga_colormap_type == 1 ) { // colormapped (paletted) image\n        if (tga_image_type != 1 && tga_image_type != 9) {\n            stbi__rewind(s);\n            return 0;\n        }\n        stbi__skip(s,4);       // skip index of first colormap entry and number of entries\n        sz = stbi__get8(s);    //   check bits per palette color entry\n        if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {\n            stbi__rewind(s);\n            return 0;\n        }\n        stbi__skip(s,4);       // skip image x and y origin\n        tga_colormap_bpp = sz;\n    } else { // \"normal\" image w/o colormap - only RGB or grey allowed, +/- RLE\n        if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {\n            stbi__rewind(s);\n            return 0; // only RGB or grey allowed, +/- RLE\n        }\n        stbi__skip(s,9); // skip colormap specification and image x/y origin\n        tga_colormap_bpp = 0;\n    }\n    tga_w = stbi__get16le(s);\n    if( tga_w < 1 ) {\n        stbi__rewind(s);\n        return 0;   // test width\n    }\n    tga_h = stbi__get16le(s);\n    if( tga_h < 1 ) {\n        stbi__rewind(s);\n        return 0;   // test height\n    }\n    tga_bits_per_pixel = stbi__get8(s); // bits per pixel\n    stbi__get8(s); // ignore alpha bits\n    if (tga_colormap_bpp != 0) {\n        if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {\n            // when using a colormap, tga_bits_per_pixel is the size of the indexes\n            // I don't think anything but 8 or 16bit indexes makes sense\n            stbi__rewind(s);\n            return 0;\n        }\n        tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);\n    } else {\n        tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);\n    }\n    if(!tga_comp) {\n      stbi__rewind(s);\n      return 0;\n    }\n    if (x) *x = tga_w;\n    if (y) *y = tga_h;\n    if (comp) *comp = tga_comp;\n    return 1;                   // seems to have passed everything\n}\n\nstatic int stbi__tga_test(stbi__context *s)\n{\n   int res = 0;\n   int sz, tga_color_type;\n   stbi__get8(s);      //   discard Offset\n   tga_color_type = stbi__get8(s);   //   color type\n   if ( tga_color_type > 1 ) goto errorEnd;   //   only RGB or indexed allowed\n   sz = stbi__get8(s);   //   image type\n   if ( tga_color_type == 1 ) { // colormapped (paletted) image\n      if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9\n      stbi__skip(s,4);       // skip index of first colormap entry and number of entries\n      sz = stbi__get8(s);    //   check bits per palette color entry\n      if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;\n      stbi__skip(s,4);       // skip image x and y origin\n   } else { // \"normal\" image w/o colormap\n      if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE\n      stbi__skip(s,9); // skip colormap specification and image x/y origin\n   }\n   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test width\n   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test height\n   sz = stbi__get8(s);   //   bits per pixel\n   if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index\n   if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;\n\n   res = 1; // if we got this far, everything's good and we can return 1 instead of 0\n\nerrorEnd:\n   stbi__rewind(s);\n   return res;\n}\n\n// read 16bit value and convert to 24bit RGB\nstatic void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)\n{\n   stbi__uint16 px = (stbi__uint16)stbi__get16le(s);\n   stbi__uint16 fiveBitMask = 31;\n   // we have 3 channels with 5bits each\n   int r = (px >> 10) & fiveBitMask;\n   int g = (px >> 5) & fiveBitMask;\n   int b = px & fiveBitMask;\n   // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later\n   out[0] = (stbi_uc)((r * 255)/31);\n   out[1] = (stbi_uc)((g * 255)/31);\n   out[2] = (stbi_uc)((b * 255)/31);\n\n   // some people claim that the most significant bit might be used for alpha\n   // (possibly if an alpha-bit is set in the \"image descriptor byte\")\n   // but that only made 16bit test images completely translucent..\n   // so let's treat all 15 and 16bit TGAs as RGB with no alpha.\n}\n\nstatic void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   //   read in the TGA header stuff\n   int tga_offset = stbi__get8(s);\n   int tga_indexed = stbi__get8(s);\n   int tga_image_type = stbi__get8(s);\n   int tga_is_RLE = 0;\n   int tga_palette_start = stbi__get16le(s);\n   int tga_palette_len = stbi__get16le(s);\n   int tga_palette_bits = stbi__get8(s);\n   int tga_x_origin = stbi__get16le(s);\n   int tga_y_origin = stbi__get16le(s);\n   int tga_width = stbi__get16le(s);\n   int tga_height = stbi__get16le(s);\n   int tga_bits_per_pixel = stbi__get8(s);\n   int tga_comp, tga_rgb16=0;\n   int tga_inverted = stbi__get8(s);\n   // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)\n   //   image data\n   unsigned char *tga_data;\n   unsigned char *tga_palette = NULL;\n   int i, j;\n   unsigned char raw_data[4] = {0};\n   int RLE_count = 0;\n   int RLE_repeating = 0;\n   int read_next_pixel = 1;\n   STBI_NOTUSED(ri);\n   STBI_NOTUSED(tga_x_origin); // @TODO\n   STBI_NOTUSED(tga_y_origin); // @TODO\n\n   if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   //   do a tiny bit of precessing\n   if ( tga_image_type >= 8 )\n   {\n      tga_image_type -= 8;\n      tga_is_RLE = 1;\n   }\n   tga_inverted = 1 - ((tga_inverted >> 5) & 1);\n\n   //   If I'm paletted, then I'll use the number of bits from the palette\n   if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);\n   else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);\n\n   if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency\n      return stbi__errpuc(\"bad format\", \"Can't find out TGA pixelformat\");\n\n   //   tga info\n   *x = tga_width;\n   *y = tga_height;\n   if (comp) *comp = tga_comp;\n\n   if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt TGA\");\n\n   tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);\n   if (!tga_data) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n   // skip to the data's starting position (offset usually = 0)\n   stbi__skip(s, tga_offset );\n\n   if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {\n      for (i=0; i < tga_height; ++i) {\n         int row = tga_inverted ? tga_height -i - 1 : i;\n         stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;\n         stbi__getn(s, tga_row, tga_width * tga_comp);\n      }\n   } else  {\n      //   do I need to load a palette?\n      if ( tga_indexed)\n      {\n         if (tga_palette_len == 0) {  /* you have to have at least one entry! */\n            STBI_FREE(tga_data);\n            return stbi__errpuc(\"bad palette\", \"Corrupt TGA\");\n         }\n\n         //   any data to skip? (offset usually = 0)\n         stbi__skip(s, tga_palette_start );\n         //   load the palette\n         tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);\n         if (!tga_palette) {\n            STBI_FREE(tga_data);\n            return stbi__errpuc(\"outofmem\", \"Out of memory\");\n         }\n         if (tga_rgb16) {\n            stbi_uc *pal_entry = tga_palette;\n            STBI_ASSERT(tga_comp == STBI_rgb);\n            for (i=0; i < tga_palette_len; ++i) {\n               stbi__tga_read_rgb16(s, pal_entry);\n               pal_entry += tga_comp;\n            }\n         } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {\n               STBI_FREE(tga_data);\n               STBI_FREE(tga_palette);\n               return stbi__errpuc(\"bad palette\", \"Corrupt TGA\");\n         }\n      }\n      //   load the data\n      for (i=0; i < tga_width * tga_height; ++i)\n      {\n         //   if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?\n         if ( tga_is_RLE )\n         {\n            if ( RLE_count == 0 )\n            {\n               //   yep, get the next byte as a RLE command\n               int RLE_cmd = stbi__get8(s);\n               RLE_count = 1 + (RLE_cmd & 127);\n               RLE_repeating = RLE_cmd >> 7;\n               read_next_pixel = 1;\n            } else if ( !RLE_repeating )\n            {\n               read_next_pixel = 1;\n            }\n         } else\n         {\n            read_next_pixel = 1;\n         }\n         //   OK, if I need to read a pixel, do it now\n         if ( read_next_pixel )\n         {\n            //   load however much data we did have\n            if ( tga_indexed )\n            {\n               // read in index, then perform the lookup\n               int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);\n               if ( pal_idx >= tga_palette_len ) {\n                  // invalid index\n                  pal_idx = 0;\n               }\n               pal_idx *= tga_comp;\n               for (j = 0; j < tga_comp; ++j) {\n                  raw_data[j] = tga_palette[pal_idx+j];\n               }\n            } else if(tga_rgb16) {\n               STBI_ASSERT(tga_comp == STBI_rgb);\n               stbi__tga_read_rgb16(s, raw_data);\n            } else {\n               //   read in the data raw\n               for (j = 0; j < tga_comp; ++j) {\n                  raw_data[j] = stbi__get8(s);\n               }\n            }\n            //   clear the reading flag for the next pixel\n            read_next_pixel = 0;\n         } // end of reading a pixel\n\n         // copy data\n         for (j = 0; j < tga_comp; ++j)\n           tga_data[i*tga_comp+j] = raw_data[j];\n\n         //   in case we're in RLE mode, keep counting down\n         --RLE_count;\n      }\n      //   do I need to invert the image?\n      if ( tga_inverted )\n      {\n         for (j = 0; j*2 < tga_height; ++j)\n         {\n            int index1 = j * tga_width * tga_comp;\n            int index2 = (tga_height - 1 - j) * tga_width * tga_comp;\n            for (i = tga_width * tga_comp; i > 0; --i)\n            {\n               unsigned char temp = tga_data[index1];\n               tga_data[index1] = tga_data[index2];\n               tga_data[index2] = temp;\n               ++index1;\n               ++index2;\n            }\n         }\n      }\n      //   clear my palette, if I had one\n      if ( tga_palette != NULL )\n      {\n         STBI_FREE( tga_palette );\n      }\n   }\n\n   // swap RGB - if the source data was RGB16, it already is in the right order\n   if (tga_comp >= 3 && !tga_rgb16)\n   {\n      unsigned char* tga_pixel = tga_data;\n      for (i=0; i < tga_width * tga_height; ++i)\n      {\n         unsigned char temp = tga_pixel[0];\n         tga_pixel[0] = tga_pixel[2];\n         tga_pixel[2] = temp;\n         tga_pixel += tga_comp;\n      }\n   }\n\n   // convert to target component count\n   if (req_comp && req_comp != tga_comp)\n      tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);\n\n   //   the things I do to get rid of an error message, and yet keep\n   //   Microsoft's C compilers happy... [8^(\n   tga_palette_start = tga_palette_len = tga_palette_bits =\n         tga_x_origin = tga_y_origin = 0;\n   STBI_NOTUSED(tga_palette_start);\n   //   OK, done\n   return tga_data;\n}\n#endif\n\n// *************************************************************************************************\n// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB\n\n#ifndef STBI_NO_PSD\nstatic int stbi__psd_test(stbi__context *s)\n{\n   int r = (stbi__get32be(s) == 0x38425053);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)\n{\n   int count, nleft, len;\n\n   count = 0;\n   while ((nleft = pixelCount - count) > 0) {\n      len = stbi__get8(s);\n      if (len == 128) {\n         // No-op.\n      } else if (len < 128) {\n         // Copy next len+1 bytes literally.\n         len++;\n         if (len > nleft) return 0; // corrupt data\n         count += len;\n         while (len) {\n            *p = stbi__get8(s);\n            p += 4;\n            len--;\n         }\n      } else if (len > 128) {\n         stbi_uc   val;\n         // Next -len+1 bytes in the dest are replicated from next source byte.\n         // (Interpret len as a negative 8-bit int.)\n         len = 257 - len;\n         if (len > nleft) return 0; // corrupt data\n         val = stbi__get8(s);\n         count += len;\n         while (len) {\n            *p = val;\n            p += 4;\n            len--;\n         }\n      }\n   }\n\n   return 1;\n}\n\nstatic void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)\n{\n   int pixelCount;\n   int channelCount, compression;\n   int channel, i;\n   int bitdepth;\n   int w,h;\n   stbi_uc *out;\n   STBI_NOTUSED(ri);\n\n   // Check identifier\n   if (stbi__get32be(s) != 0x38425053)   // \"8BPS\"\n      return stbi__errpuc(\"not PSD\", \"Corrupt PSD image\");\n\n   // Check file type version.\n   if (stbi__get16be(s) != 1)\n      return stbi__errpuc(\"wrong version\", \"Unsupported version of PSD image\");\n\n   // Skip 6 reserved bytes.\n   stbi__skip(s, 6 );\n\n   // Read the number of channels (R, G, B, A, etc).\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16)\n      return stbi__errpuc(\"wrong channel count\", \"Unsupported number of channels in PSD image\");\n\n   // Read the rows and columns of the image.\n   h = stbi__get32be(s);\n   w = stbi__get32be(s);\n\n   if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   // Make sure the depth is 8 bits.\n   bitdepth = stbi__get16be(s);\n   if (bitdepth != 8 && bitdepth != 16)\n      return stbi__errpuc(\"unsupported bit depth\", \"PSD bit depth is not 8 or 16 bit\");\n\n   // Make sure the color mode is RGB.\n   // Valid options are:\n   //   0: Bitmap\n   //   1: Grayscale\n   //   2: Indexed color\n   //   3: RGB color\n   //   4: CMYK color\n   //   7: Multichannel\n   //   8: Duotone\n   //   9: Lab color\n   if (stbi__get16be(s) != 3)\n      return stbi__errpuc(\"wrong color format\", \"PSD is not in RGB color format\");\n\n   // Skip the Mode Data.  (It's the palette for indexed color; other info for other modes.)\n   stbi__skip(s,stbi__get32be(s) );\n\n   // Skip the image resources.  (resolution, pen tool paths, etc)\n   stbi__skip(s, stbi__get32be(s) );\n\n   // Skip the reserved data.\n   stbi__skip(s, stbi__get32be(s) );\n\n   // Find out if the data is compressed.\n   // Known values:\n   //   0: no compression\n   //   1: RLE compressed\n   compression = stbi__get16be(s);\n   if (compression > 1)\n      return stbi__errpuc(\"bad compression\", \"PSD has an unknown compression format\");\n\n   // Check size\n   if (!stbi__mad3sizes_valid(4, w, h, 0))\n      return stbi__errpuc(\"too large\", \"Corrupt PSD\");\n\n   // Create the destination image.\n\n   if (!compression && bitdepth == 16 && bpc == 16) {\n      out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);\n      ri->bits_per_channel = 16;\n   } else\n      out = (stbi_uc *) stbi__malloc(4 * w*h);\n\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   pixelCount = w*h;\n\n   // Initialize the data to zero.\n   //memset( out, 0, pixelCount * 4 );\n\n   // Finally, the image data.\n   if (compression) {\n      // RLE as used by .PSD and .TIFF\n      // Loop until you get the number of unpacked bytes you are expecting:\n      //     Read the next source byte into n.\n      //     If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.\n      //     Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.\n      //     Else if n is 128, noop.\n      // Endloop\n\n      // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,\n      // which we're going to just skip.\n      stbi__skip(s, h * channelCount * 2 );\n\n      // Read the RLE data by channel.\n      for (channel = 0; channel < 4; channel++) {\n         stbi_uc *p;\n\n         p = out+channel;\n         if (channel >= channelCount) {\n            // Fill this channel with default data.\n            for (i = 0; i < pixelCount; i++, p += 4)\n               *p = (channel == 3 ? 255 : 0);\n         } else {\n            // Read the RLE data.\n            if (!stbi__psd_decode_rle(s, p, pixelCount)) {\n               STBI_FREE(out);\n               return stbi__errpuc(\"corrupt\", \"bad RLE data\");\n            }\n         }\n      }\n\n   } else {\n      // We're at the raw image data.  It's each channel in order (Red, Green, Blue, Alpha, ...)\n      // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.\n\n      // Read the data by channel.\n      for (channel = 0; channel < 4; channel++) {\n         if (channel >= channelCount) {\n            // Fill this channel with default data.\n            if (bitdepth == 16 && bpc == 16) {\n               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;\n               stbi__uint16 val = channel == 3 ? 65535 : 0;\n               for (i = 0; i < pixelCount; i++, q += 4)\n                  *q = val;\n            } else {\n               stbi_uc *p = out+channel;\n               stbi_uc val = channel == 3 ? 255 : 0;\n               for (i = 0; i < pixelCount; i++, p += 4)\n                  *p = val;\n            }\n         } else {\n            if (ri->bits_per_channel == 16) {    // output bpc\n               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;\n               for (i = 0; i < pixelCount; i++, q += 4)\n                  *q = (stbi__uint16) stbi__get16be(s);\n            } else {\n               stbi_uc *p = out+channel;\n               if (bitdepth == 16) {  // input bpc\n                  for (i = 0; i < pixelCount; i++, p += 4)\n                     *p = (stbi_uc) (stbi__get16be(s) >> 8);\n               } else {\n                  for (i = 0; i < pixelCount; i++, p += 4)\n                     *p = stbi__get8(s);\n               }\n            }\n         }\n      }\n   }\n\n   // remove weird white matte from PSD\n   if (channelCount >= 4) {\n      if (ri->bits_per_channel == 16) {\n         for (i=0; i < w*h; ++i) {\n            stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;\n            if (pixel[3] != 0 && pixel[3] != 65535) {\n               float a = pixel[3] / 65535.0f;\n               float ra = 1.0f / a;\n               float inv_a = 65535.0f * (1 - ra);\n               pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);\n               pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);\n               pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);\n            }\n         }\n      } else {\n         for (i=0; i < w*h; ++i) {\n            unsigned char *pixel = out + 4*i;\n            if (pixel[3] != 0 && pixel[3] != 255) {\n               float a = pixel[3] / 255.0f;\n               float ra = 1.0f / a;\n               float inv_a = 255.0f * (1 - ra);\n               pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);\n               pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);\n               pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);\n            }\n         }\n      }\n   }\n\n   // convert to desired output format\n   if (req_comp && req_comp != 4) {\n      if (ri->bits_per_channel == 16)\n         out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);\n      else\n         out = stbi__convert_format(out, 4, req_comp, w, h);\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n\n   if (comp) *comp = 4;\n   *y = h;\n   *x = w;\n\n   return out;\n}\n#endif\n\n// *************************************************************************************************\n// Softimage PIC loader\n// by Tom Seddon\n//\n// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format\n// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/\n\n#ifndef STBI_NO_PIC\nstatic int stbi__pic_is4(stbi__context *s,const char *str)\n{\n   int i;\n   for (i=0; i<4; ++i)\n      if (stbi__get8(s) != (stbi_uc)str[i])\n         return 0;\n\n   return 1;\n}\n\nstatic int stbi__pic_test_core(stbi__context *s)\n{\n   int i;\n\n   if (!stbi__pic_is4(s,\"\\x53\\x80\\xF6\\x34\"))\n      return 0;\n\n   for(i=0;i<84;++i)\n      stbi__get8(s);\n\n   if (!stbi__pic_is4(s,\"PICT\"))\n      return 0;\n\n   return 1;\n}\n\ntypedef struct\n{\n   stbi_uc size,type,channel;\n} stbi__pic_packet;\n\nstatic stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)\n{\n   int mask=0x80, i;\n\n   for (i=0; i<4; ++i, mask>>=1) {\n      if (channel & mask) {\n         if (stbi__at_eof(s)) return stbi__errpuc(\"bad file\",\"PIC file too short\");\n         dest[i]=stbi__get8(s);\n      }\n   }\n\n   return dest;\n}\n\nstatic void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)\n{\n   int mask=0x80,i;\n\n   for (i=0;i<4; ++i, mask>>=1)\n      if (channel&mask)\n         dest[i]=src[i];\n}\n\nstatic stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)\n{\n   int act_comp=0,num_packets=0,y,chained;\n   stbi__pic_packet packets[10];\n\n   // this will (should...) cater for even some bizarre stuff like having data\n    // for the same channel in multiple packets.\n   do {\n      stbi__pic_packet *packet;\n\n      if (num_packets==sizeof(packets)/sizeof(packets[0]))\n         return stbi__errpuc(\"bad format\",\"too many packets\");\n\n      packet = &packets[num_packets++];\n\n      chained = stbi__get8(s);\n      packet->size    = stbi__get8(s);\n      packet->type    = stbi__get8(s);\n      packet->channel = stbi__get8(s);\n\n      act_comp |= packet->channel;\n\n      if (stbi__at_eof(s))          return stbi__errpuc(\"bad file\",\"file too short (reading packets)\");\n      if (packet->size != 8)  return stbi__errpuc(\"bad format\",\"packet isn't 8bpp\");\n   } while (chained);\n\n   *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?\n\n   for(y=0; y<height; ++y) {\n      int packet_idx;\n\n      for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {\n         stbi__pic_packet *packet = &packets[packet_idx];\n         stbi_uc *dest = result+y*width*4;\n\n         switch (packet->type) {\n            default:\n               return stbi__errpuc(\"bad format\",\"packet has bad compression type\");\n\n            case 0: {//uncompressed\n               int x;\n\n               for(x=0;x<width;++x, dest+=4)\n                  if (!stbi__readval(s,packet->channel,dest))\n                     return 0;\n               break;\n            }\n\n            case 1://Pure RLE\n               {\n                  int left=width, i;\n\n                  while (left>0) {\n                     stbi_uc count,value[4];\n\n                     count=stbi__get8(s);\n                     if (stbi__at_eof(s))   return stbi__errpuc(\"bad file\",\"file too short (pure read count)\");\n\n                     if (count > left)\n                        count = (stbi_uc) left;\n\n                     if (!stbi__readval(s,packet->channel,value))  return 0;\n\n                     for(i=0; i<count; ++i,dest+=4)\n                        stbi__copyval(packet->channel,dest,value);\n                     left -= count;\n                  }\n               }\n               break;\n\n            case 2: {//Mixed RLE\n               int left=width;\n               while (left>0) {\n                  int count = stbi__get8(s), i;\n                  if (stbi__at_eof(s))  return stbi__errpuc(\"bad file\",\"file too short (mixed read count)\");\n\n                  if (count >= 128) { // Repeated\n                     stbi_uc value[4];\n\n                     if (count==128)\n                        count = stbi__get16be(s);\n                     else\n                        count -= 127;\n                     if (count > left)\n                        return stbi__errpuc(\"bad file\",\"scanline overrun\");\n\n                     if (!stbi__readval(s,packet->channel,value))\n                        return 0;\n\n                     for(i=0;i<count;++i, dest += 4)\n                        stbi__copyval(packet->channel,dest,value);\n                  } else { // Raw\n                     ++count;\n                     if (count>left) return stbi__errpuc(\"bad file\",\"scanline overrun\");\n\n                     for(i=0;i<count;++i, dest+=4)\n                        if (!stbi__readval(s,packet->channel,dest))\n                           return 0;\n                  }\n                  left-=count;\n               }\n               break;\n            }\n         }\n      }\n   }\n\n   return result;\n}\n\nstatic void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *result;\n   int i, x,y, internal_comp;\n   STBI_NOTUSED(ri);\n\n   if (!comp) comp = &internal_comp;\n\n   for (i=0; i<92; ++i)\n      stbi__get8(s);\n\n   x = stbi__get16be(s);\n   y = stbi__get16be(s);\n\n   if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   if (stbi__at_eof(s))  return stbi__errpuc(\"bad file\",\"file too short (pic header)\");\n   if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc(\"too large\", \"PIC image too large to decode\");\n\n   stbi__get32be(s); //skip `ratio'\n   stbi__get16be(s); //skip `fields'\n   stbi__get16be(s); //skip `pad'\n\n   // intermediate buffer is RGBA\n   result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);\n   if (!result) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   memset(result, 0xff, x*y*4);\n\n   if (!stbi__pic_load_core(s,x,y,comp, result)) {\n      STBI_FREE(result);\n      result=0;\n   }\n   *px = x;\n   *py = y;\n   if (req_comp == 0) req_comp = *comp;\n   result=stbi__convert_format(result,4,req_comp,x,y);\n\n   return result;\n}\n\nstatic int stbi__pic_test(stbi__context *s)\n{\n   int r = stbi__pic_test_core(s);\n   stbi__rewind(s);\n   return r;\n}\n#endif\n\n// *************************************************************************************************\n// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb\n\n#ifndef STBI_NO_GIF\ntypedef struct\n{\n   stbi__int16 prefix;\n   stbi_uc first;\n   stbi_uc suffix;\n} stbi__gif_lzw;\n\ntypedef struct\n{\n   int w,h;\n   stbi_uc *out;                 // output buffer (always 4 components)\n   stbi_uc *background;          // The current \"background\" as far as a gif is concerned\n   stbi_uc *history;\n   int flags, bgindex, ratio, transparent, eflags;\n   stbi_uc  pal[256][4];\n   stbi_uc lpal[256][4];\n   stbi__gif_lzw codes[8192];\n   stbi_uc *color_table;\n   int parse, step;\n   int lflags;\n   int start_x, start_y;\n   int max_x, max_y;\n   int cur_x, cur_y;\n   int line_size;\n   int delay;\n} stbi__gif;\n\nstatic int stbi__gif_test_raw(stbi__context *s)\n{\n   int sz;\n   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;\n   sz = stbi__get8(s);\n   if (sz != '9' && sz != '7') return 0;\n   if (stbi__get8(s) != 'a') return 0;\n   return 1;\n}\n\nstatic int stbi__gif_test(stbi__context *s)\n{\n   int r = stbi__gif_test_raw(s);\n   stbi__rewind(s);\n   return r;\n}\n\nstatic void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)\n{\n   int i;\n   for (i=0; i < num_entries; ++i) {\n      pal[i][2] = stbi__get8(s);\n      pal[i][1] = stbi__get8(s);\n      pal[i][0] = stbi__get8(s);\n      pal[i][3] = transp == i ? 0 : 255;\n   }\n}\n\nstatic int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)\n{\n   stbi_uc version;\n   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')\n      return stbi__err(\"not GIF\", \"Corrupt GIF\");\n\n   version = stbi__get8(s);\n   if (version != '7' && version != '9')    return stbi__err(\"not GIF\", \"Corrupt GIF\");\n   if (stbi__get8(s) != 'a')                return stbi__err(\"not GIF\", \"Corrupt GIF\");\n\n   stbi__g_failure_reason = \"\";\n   g->w = stbi__get16le(s);\n   g->h = stbi__get16le(s);\n   g->flags = stbi__get8(s);\n   g->bgindex = stbi__get8(s);\n   g->ratio = stbi__get8(s);\n   g->transparent = -1;\n\n   if (g->w > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n   if (g->h > STBI_MAX_DIMENSIONS) return stbi__err(\"too large\",\"Very large image (corrupt?)\");\n\n   if (comp != 0) *comp = 4;  // can't actually tell whether it's 3 or 4 until we parse the comments\n\n   if (is_info) return 1;\n\n   if (g->flags & 0x80)\n      stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);\n\n   return 1;\n}\n\nstatic int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)\n{\n   stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));\n   if (!g) return stbi__err(\"outofmem\", \"Out of memory\");\n   if (!stbi__gif_header(s, g, comp, 1)) {\n      STBI_FREE(g);\n      stbi__rewind( s );\n      return 0;\n   }\n   if (x) *x = g->w;\n   if (y) *y = g->h;\n   STBI_FREE(g);\n   return 1;\n}\n\nstatic void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)\n{\n   stbi_uc *p, *c;\n   int idx;\n\n   // recurse to decode the prefixes, since the linked-list is backwards,\n   // and working backwards through an interleaved image would be nasty\n   if (g->codes[code].prefix >= 0)\n      stbi__out_gif_code(g, g->codes[code].prefix);\n\n   if (g->cur_y >= g->max_y) return;\n\n   idx = g->cur_x + g->cur_y;\n   p = &g->out[idx];\n   g->history[idx / 4] = 1;\n\n   c = &g->color_table[g->codes[code].suffix * 4];\n   if (c[3] > 128) { // don't render transparent pixels;\n      p[0] = c[2];\n      p[1] = c[1];\n      p[2] = c[0];\n      p[3] = c[3];\n   }\n   g->cur_x += 4;\n\n   if (g->cur_x >= g->max_x) {\n      g->cur_x = g->start_x;\n      g->cur_y += g->step;\n\n      while (g->cur_y >= g->max_y && g->parse > 0) {\n         g->step = (1 << g->parse) * g->line_size;\n         g->cur_y = g->start_y + (g->step >> 1);\n         --g->parse;\n      }\n   }\n}\n\nstatic stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)\n{\n   stbi_uc lzw_cs;\n   stbi__int32 len, init_code;\n   stbi__uint32 first;\n   stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;\n   stbi__gif_lzw *p;\n\n   lzw_cs = stbi__get8(s);\n   if (lzw_cs > 12) return NULL;\n   clear = 1 << lzw_cs;\n   first = 1;\n   codesize = lzw_cs + 1;\n   codemask = (1 << codesize) - 1;\n   bits = 0;\n   valid_bits = 0;\n   for (init_code = 0; init_code < clear; init_code++) {\n      g->codes[init_code].prefix = -1;\n      g->codes[init_code].first = (stbi_uc) init_code;\n      g->codes[init_code].suffix = (stbi_uc) init_code;\n   }\n\n   // support no starting clear code\n   avail = clear+2;\n   oldcode = -1;\n\n   len = 0;\n   for(;;) {\n      if (valid_bits < codesize) {\n         if (len == 0) {\n            len = stbi__get8(s); // start new block\n            if (len == 0)\n               return g->out;\n         }\n         --len;\n         bits |= (stbi__int32) stbi__get8(s) << valid_bits;\n         valid_bits += 8;\n      } else {\n         stbi__int32 code = bits & codemask;\n         bits >>= codesize;\n         valid_bits -= codesize;\n         // @OPTIMIZE: is there some way we can accelerate the non-clear path?\n         if (code == clear) {  // clear code\n            codesize = lzw_cs + 1;\n            codemask = (1 << codesize) - 1;\n            avail = clear + 2;\n            oldcode = -1;\n            first = 0;\n         } else if (code == clear + 1) { // end of stream code\n            stbi__skip(s, len);\n            while ((len = stbi__get8(s)) > 0)\n               stbi__skip(s,len);\n            return g->out;\n         } else if (code <= avail) {\n            if (first) {\n               return stbi__errpuc(\"no clear code\", \"Corrupt GIF\");\n            }\n\n            if (oldcode >= 0) {\n               p = &g->codes[avail++];\n               if (avail > 8192) {\n                  return stbi__errpuc(\"too many codes\", \"Corrupt GIF\");\n               }\n\n               p->prefix = (stbi__int16) oldcode;\n               p->first = g->codes[oldcode].first;\n               p->suffix = (code == avail) ? p->first : g->codes[code].first;\n            } else if (code == avail)\n               return stbi__errpuc(\"illegal code in raster\", \"Corrupt GIF\");\n\n            stbi__out_gif_code(g, (stbi__uint16) code);\n\n            if ((avail & codemask) == 0 && avail <= 0x0FFF) {\n               codesize++;\n               codemask = (1 << codesize) - 1;\n            }\n\n            oldcode = code;\n         } else {\n            return stbi__errpuc(\"illegal code in raster\", \"Corrupt GIF\");\n         }\n      }\n   }\n}\n\n// this function is designed to support animated gifs, although stb_image doesn't support it\n// two back is the image from two frames ago, used for a very specific disposal format\nstatic stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)\n{\n   int dispose;\n   int first_frame;\n   int pi;\n   int pcount;\n   STBI_NOTUSED(req_comp);\n\n   // on first frame, any non-written pixels get the background colour (non-transparent)\n   first_frame = 0;\n   if (g->out == 0) {\n      if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header\n      if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))\n         return stbi__errpuc(\"too large\", \"GIF image is too large\");\n      pcount = g->w * g->h;\n      g->out = (stbi_uc *) stbi__malloc(4 * pcount);\n      g->background = (stbi_uc *) stbi__malloc(4 * pcount);\n      g->history = (stbi_uc *) stbi__malloc(pcount);\n      if (!g->out || !g->background || !g->history)\n         return stbi__errpuc(\"outofmem\", \"Out of memory\");\n\n      // image is treated as \"transparent\" at the start - ie, nothing overwrites the current background;\n      // background colour is only used for pixels that are not rendered first frame, after that \"background\"\n      // color refers to the color that was there the previous frame.\n      memset(g->out, 0x00, 4 * pcount);\n      memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)\n      memset(g->history, 0x00, pcount);        // pixels that were affected previous frame\n      first_frame = 1;\n   } else {\n      // second frame - how do we dispose of the previous one?\n      dispose = (g->eflags & 0x1C) >> 2;\n      pcount = g->w * g->h;\n\n      if ((dispose == 3) && (two_back == 0)) {\n         dispose = 2; // if I don't have an image to revert back to, default to the old background\n      }\n\n      if (dispose == 3) { // use previous graphic\n         for (pi = 0; pi < pcount; ++pi) {\n            if (g->history[pi]) {\n               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );\n            }\n         }\n      } else if (dispose == 2) {\n         // restore what was changed last frame to background before that frame;\n         for (pi = 0; pi < pcount; ++pi) {\n            if (g->history[pi]) {\n               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );\n            }\n         }\n      } else {\n         // This is a non-disposal case eithe way, so just\n         // leave the pixels as is, and they will become the new background\n         // 1: do not dispose\n         // 0:  not specified.\n      }\n\n      // background is what out is after the undoing of the previou frame;\n      memcpy( g->background, g->out, 4 * g->w * g->h );\n   }\n\n   // clear my history;\n   memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame\n\n   for (;;) {\n      int tag = stbi__get8(s);\n      switch (tag) {\n         case 0x2C: /* Image Descriptor */\n         {\n            stbi__int32 x, y, w, h;\n            stbi_uc *o;\n\n            x = stbi__get16le(s);\n            y = stbi__get16le(s);\n            w = stbi__get16le(s);\n            h = stbi__get16le(s);\n            if (((x + w) > (g->w)) || ((y + h) > (g->h)))\n               return stbi__errpuc(\"bad Image Descriptor\", \"Corrupt GIF\");\n\n            g->line_size = g->w * 4;\n            g->start_x = x * 4;\n            g->start_y = y * g->line_size;\n            g->max_x   = g->start_x + w * 4;\n            g->max_y   = g->start_y + h * g->line_size;\n            g->cur_x   = g->start_x;\n            g->cur_y   = g->start_y;\n\n            // if the width of the specified rectangle is 0, that means\n            // we may not see *any* pixels or the image is malformed;\n            // to make sure this is caught, move the current y down to\n            // max_y (which is what out_gif_code checks).\n            if (w == 0)\n               g->cur_y = g->max_y;\n\n            g->lflags = stbi__get8(s);\n\n            if (g->lflags & 0x40) {\n               g->step = 8 * g->line_size; // first interlaced spacing\n               g->parse = 3;\n            } else {\n               g->step = g->line_size;\n               g->parse = 0;\n            }\n\n            if (g->lflags & 0x80) {\n               stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);\n               g->color_table = (stbi_uc *) g->lpal;\n            } else if (g->flags & 0x80) {\n               g->color_table = (stbi_uc *) g->pal;\n            } else\n               return stbi__errpuc(\"missing color table\", \"Corrupt GIF\");\n\n            o = stbi__process_gif_raster(s, g);\n            if (!o) return NULL;\n\n            // if this was the first frame,\n            pcount = g->w * g->h;\n            if (first_frame && (g->bgindex > 0)) {\n               // if first frame, any pixel not drawn to gets the background color\n               for (pi = 0; pi < pcount; ++pi) {\n                  if (g->history[pi] == 0) {\n                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;\n                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );\n                  }\n               }\n            }\n\n            return o;\n         }\n\n         case 0x21: // Comment Extension.\n         {\n            int len;\n            int ext = stbi__get8(s);\n            if (ext == 0xF9) { // Graphic Control Extension.\n               len = stbi__get8(s);\n               if (len == 4) {\n                  g->eflags = stbi__get8(s);\n                  g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.\n\n                  // unset old transparent\n                  if (g->transparent >= 0) {\n                     g->pal[g->transparent][3] = 255;\n                  }\n                  if (g->eflags & 0x01) {\n                     g->transparent = stbi__get8(s);\n                     if (g->transparent >= 0) {\n                        g->pal[g->transparent][3] = 0;\n                     }\n                  } else {\n                     // don't need transparent\n                     stbi__skip(s, 1);\n                     g->transparent = -1;\n                  }\n               } else {\n                  stbi__skip(s, len);\n                  break;\n               }\n            }\n            while ((len = stbi__get8(s)) != 0) {\n               stbi__skip(s, len);\n            }\n            break;\n         }\n\n         case 0x3B: // gif stream termination code\n            return (stbi_uc *) s; // using '1' causes warning on some compilers\n\n         default:\n            return stbi__errpuc(\"unknown code\", \"Corrupt GIF\");\n      }\n   }\n}\n\nstatic void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays)\n{\n   STBI_FREE(g->out);\n   STBI_FREE(g->history);\n   STBI_FREE(g->background);\n\n   if (out) STBI_FREE(out);\n   if (delays && *delays) STBI_FREE(*delays);\n   return stbi__errpuc(\"outofmem\", \"Out of memory\");\n}\n\nstatic void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)\n{\n   if (stbi__gif_test(s)) {\n      int layers = 0;\n      stbi_uc *u = 0;\n      stbi_uc *out = 0;\n      stbi_uc *two_back = 0;\n      stbi__gif g;\n      int stride;\n      int out_size = 0;\n      int delays_size = 0;\n\n      STBI_NOTUSED(out_size);\n      STBI_NOTUSED(delays_size);\n\n      memset(&g, 0, sizeof(g));\n      if (delays) {\n         *delays = 0;\n      }\n\n      do {\n         u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);\n         if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker\n\n         if (u) {\n            *x = g.w;\n            *y = g.h;\n            ++layers;\n            stride = g.w * g.h * 4;\n\n            if (out) {\n               void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride );\n               if (!tmp)\n                  return stbi__load_gif_main_outofmem(&g, out, delays);\n               else {\n                   out = (stbi_uc*) tmp;\n                   out_size = layers * stride;\n               }\n\n               if (delays) {\n                  int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers );\n                  if (!new_delays)\n                     return stbi__load_gif_main_outofmem(&g, out, delays);\n                  *delays = new_delays;\n                  delays_size = layers * sizeof(int);\n               }\n            } else {\n               out = (stbi_uc*)stbi__malloc( layers * stride );\n               if (!out)\n                  return stbi__load_gif_main_outofmem(&g, out, delays);\n               out_size = layers * stride;\n               if (delays) {\n                  *delays = (int*) stbi__malloc( layers * sizeof(int) );\n                  if (!*delays)\n                     return stbi__load_gif_main_outofmem(&g, out, delays);\n                  delays_size = layers * sizeof(int);\n               }\n            }\n            memcpy( out + ((layers - 1) * stride), u, stride );\n            if (layers >= 2) {\n               two_back = out - 2 * stride;\n            }\n\n            if (delays) {\n               (*delays)[layers - 1U] = g.delay;\n            }\n         }\n      } while (u != 0);\n\n      // free temp buffer;\n      STBI_FREE(g.out);\n      STBI_FREE(g.history);\n      STBI_FREE(g.background);\n\n      // do the final conversion after loading everything;\n      if (req_comp && req_comp != 4)\n         out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);\n\n      *z = layers;\n      return out;\n   } else {\n      return stbi__errpuc(\"not GIF\", \"Image was not as a gif type.\");\n   }\n}\n\nstatic void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *u = 0;\n   stbi__gif g;\n   memset(&g, 0, sizeof(g));\n   STBI_NOTUSED(ri);\n\n   u = stbi__gif_load_next(s, &g, comp, req_comp, 0);\n   if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker\n   if (u) {\n      *x = g.w;\n      *y = g.h;\n\n      // moved conversion to after successful load so that the same\n      // can be done for multiple frames.\n      if (req_comp && req_comp != 4)\n         u = stbi__convert_format(u, 4, req_comp, g.w, g.h);\n   } else if (g.out) {\n      // if there was an error and we allocated an image buffer, free it!\n      STBI_FREE(g.out);\n   }\n\n   // free buffers needed for multiple frame loading;\n   STBI_FREE(g.history);\n   STBI_FREE(g.background);\n\n   return u;\n}\n\nstatic int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   return stbi__gif_info_raw(s,x,y,comp);\n}\n#endif\n\n// *************************************************************************************************\n// Radiance RGBE HDR loader\n// originally by Nicolas Schulz\n#ifndef STBI_NO_HDR\nstatic int stbi__hdr_test_core(stbi__context *s, const char *signature)\n{\n   int i;\n   for (i=0; signature[i]; ++i)\n      if (stbi__get8(s) != signature[i])\n          return 0;\n   stbi__rewind(s);\n   return 1;\n}\n\nstatic int stbi__hdr_test(stbi__context* s)\n{\n   int r = stbi__hdr_test_core(s, \"#?RADIANCE\\n\");\n   stbi__rewind(s);\n   if(!r) {\n       r = stbi__hdr_test_core(s, \"#?RGBE\\n\");\n       stbi__rewind(s);\n   }\n   return r;\n}\n\n#define STBI__HDR_BUFLEN  1024\nstatic char *stbi__hdr_gettoken(stbi__context *z, char *buffer)\n{\n   int len=0;\n   char c = '\\0';\n\n   c = (char) stbi__get8(z);\n\n   while (!stbi__at_eof(z) && c != '\\n') {\n      buffer[len++] = c;\n      if (len == STBI__HDR_BUFLEN-1) {\n         // flush to end of line\n         while (!stbi__at_eof(z) && stbi__get8(z) != '\\n')\n            ;\n         break;\n      }\n      c = (char) stbi__get8(z);\n   }\n\n   buffer[len] = 0;\n   return buffer;\n}\n\nstatic void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)\n{\n   if ( input[3] != 0 ) {\n      float f1;\n      // Exponent\n      f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));\n      if (req_comp <= 2)\n         output[0] = (input[0] + input[1] + input[2]) * f1 / 3;\n      else {\n         output[0] = input[0] * f1;\n         output[1] = input[1] * f1;\n         output[2] = input[2] * f1;\n      }\n      if (req_comp == 2) output[1] = 1;\n      if (req_comp == 4) output[3] = 1;\n   } else {\n      switch (req_comp) {\n         case 4: output[3] = 1; /* fallthrough */\n         case 3: output[0] = output[1] = output[2] = 0;\n                 break;\n         case 2: output[1] = 1; /* fallthrough */\n         case 1: output[0] = 0;\n                 break;\n      }\n   }\n}\n\nstatic float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   char buffer[STBI__HDR_BUFLEN];\n   char *token;\n   int valid = 0;\n   int width, height;\n   stbi_uc *scanline;\n   float *hdr_data;\n   int len;\n   unsigned char count, value;\n   int i, j, k, c1,c2, z;\n   const char *headerToken;\n   STBI_NOTUSED(ri);\n\n   // Check identifier\n   headerToken = stbi__hdr_gettoken(s,buffer);\n   if (strcmp(headerToken, \"#?RADIANCE\") != 0 && strcmp(headerToken, \"#?RGBE\") != 0)\n      return stbi__errpf(\"not HDR\", \"Corrupt HDR image\");\n\n   // Parse header\n   for(;;) {\n      token = stbi__hdr_gettoken(s,buffer);\n      if (token[0] == 0) break;\n      if (strcmp(token, \"FORMAT=32-bit_rle_rgbe\") == 0) valid = 1;\n   }\n\n   if (!valid)    return stbi__errpf(\"unsupported format\", \"Unsupported HDR format\");\n\n   // Parse width and height\n   // can't use sscanf() if we're not using stdio!\n   token = stbi__hdr_gettoken(s,buffer);\n   if (strncmp(token, \"-Y \", 3))  return stbi__errpf(\"unsupported data layout\", \"Unsupported HDR format\");\n   token += 3;\n   height = (int) strtol(token, &token, 10);\n   while (*token == ' ') ++token;\n   if (strncmp(token, \"+X \", 3))  return stbi__errpf(\"unsupported data layout\", \"Unsupported HDR format\");\n   token += 3;\n   width = (int) strtol(token, NULL, 10);\n\n   if (height > STBI_MAX_DIMENSIONS) return stbi__errpf(\"too large\",\"Very large image (corrupt?)\");\n   if (width > STBI_MAX_DIMENSIONS) return stbi__errpf(\"too large\",\"Very large image (corrupt?)\");\n\n   *x = width;\n   *y = height;\n\n   if (comp) *comp = 3;\n   if (req_comp == 0) req_comp = 3;\n\n   if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))\n      return stbi__errpf(\"too large\", \"HDR image is too large\");\n\n   // Read data\n   hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);\n   if (!hdr_data)\n      return stbi__errpf(\"outofmem\", \"Out of memory\");\n\n   // Load image data\n   // image data is stored as some number of sca\n   if ( width < 8 || width >= 32768) {\n      // Read flat data\n      for (j=0; j < height; ++j) {\n         for (i=0; i < width; ++i) {\n            stbi_uc rgbe[4];\n           main_decode_loop:\n            stbi__getn(s, rgbe, 4);\n            stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);\n         }\n      }\n   } else {\n      // Read RLE-encoded data\n      scanline = NULL;\n\n      for (j = 0; j < height; ++j) {\n         c1 = stbi__get8(s);\n         c2 = stbi__get8(s);\n         len = stbi__get8(s);\n         if (c1 != 2 || c2 != 2 || (len & 0x80)) {\n            // not run-length encoded, so we have to actually use THIS data as a decoded\n            // pixel (note this can't be a valid pixel--one of RGB must be >= 128)\n            stbi_uc rgbe[4];\n            rgbe[0] = (stbi_uc) c1;\n            rgbe[1] = (stbi_uc) c2;\n            rgbe[2] = (stbi_uc) len;\n            rgbe[3] = (stbi_uc) stbi__get8(s);\n            stbi__hdr_convert(hdr_data, rgbe, req_comp);\n            i = 1;\n            j = 0;\n            STBI_FREE(scanline);\n            goto main_decode_loop; // yes, this makes no sense\n         }\n         len <<= 8;\n         len |= stbi__get8(s);\n         if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"invalid decoded scanline length\", \"corrupt HDR\"); }\n         if (scanline == NULL) {\n            scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);\n            if (!scanline) {\n               STBI_FREE(hdr_data);\n               return stbi__errpf(\"outofmem\", \"Out of memory\");\n            }\n         }\n\n         for (k = 0; k < 4; ++k) {\n            int nleft;\n            i = 0;\n            while ((nleft = width - i) > 0) {\n               count = stbi__get8(s);\n               if (count > 128) {\n                  // Run\n                  value = stbi__get8(s);\n                  count -= 128;\n                  if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"corrupt\", \"bad RLE data in HDR\"); }\n                  for (z = 0; z < count; ++z)\n                     scanline[i++ * 4 + k] = value;\n               } else {\n                  // Dump\n                  if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf(\"corrupt\", \"bad RLE data in HDR\"); }\n                  for (z = 0; z < count; ++z)\n                     scanline[i++ * 4 + k] = stbi__get8(s);\n               }\n            }\n         }\n         for (i=0; i < width; ++i)\n            stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);\n      }\n      if (scanline)\n         STBI_FREE(scanline);\n   }\n\n   return hdr_data;\n}\n\nstatic int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   char buffer[STBI__HDR_BUFLEN];\n   char *token;\n   int valid = 0;\n   int dummy;\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   if (stbi__hdr_test(s) == 0) {\n       stbi__rewind( s );\n       return 0;\n   }\n\n   for(;;) {\n      token = stbi__hdr_gettoken(s,buffer);\n      if (token[0] == 0) break;\n      if (strcmp(token, \"FORMAT=32-bit_rle_rgbe\") == 0) valid = 1;\n   }\n\n   if (!valid) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token = stbi__hdr_gettoken(s,buffer);\n   if (strncmp(token, \"-Y \", 3)) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token += 3;\n   *y = (int) strtol(token, &token, 10);\n   while (*token == ' ') ++token;\n   if (strncmp(token, \"+X \", 3)) {\n       stbi__rewind( s );\n       return 0;\n   }\n   token += 3;\n   *x = (int) strtol(token, NULL, 10);\n   *comp = 3;\n   return 1;\n}\n#endif // STBI_NO_HDR\n\n#ifndef STBI_NO_BMP\nstatic int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   void *p;\n   stbi__bmp_data info;\n\n   info.all_a = 255;\n   p = stbi__bmp_parse_header(s, &info);\n   if (p == NULL) {\n      stbi__rewind( s );\n      return 0;\n   }\n   if (x) *x = s->img_x;\n   if (y) *y = s->img_y;\n   if (comp) {\n      if (info.bpp == 24 && info.ma == 0xff000000)\n         *comp = 3;\n      else\n         *comp = info.ma ? 4 : 3;\n   }\n   return 1;\n}\n#endif\n\n#ifndef STBI_NO_PSD\nstatic int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int channelCount, dummy, depth;\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n   if (stbi__get32be(s) != 0x38425053) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 1) {\n       stbi__rewind( s );\n       return 0;\n   }\n   stbi__skip(s, 6);\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   *y = stbi__get32be(s);\n   *x = stbi__get32be(s);\n   depth = stbi__get16be(s);\n   if (depth != 8 && depth != 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 3) {\n       stbi__rewind( s );\n       return 0;\n   }\n   *comp = 4;\n   return 1;\n}\n\nstatic int stbi__psd_is16(stbi__context *s)\n{\n   int channelCount, depth;\n   if (stbi__get32be(s) != 0x38425053) {\n       stbi__rewind( s );\n       return 0;\n   }\n   if (stbi__get16be(s) != 1) {\n       stbi__rewind( s );\n       return 0;\n   }\n   stbi__skip(s, 6);\n   channelCount = stbi__get16be(s);\n   if (channelCount < 0 || channelCount > 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   STBI_NOTUSED(stbi__get32be(s));\n   STBI_NOTUSED(stbi__get32be(s));\n   depth = stbi__get16be(s);\n   if (depth != 16) {\n       stbi__rewind( s );\n       return 0;\n   }\n   return 1;\n}\n#endif\n\n#ifndef STBI_NO_PIC\nstatic int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int act_comp=0,num_packets=0,chained,dummy;\n   stbi__pic_packet packets[10];\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   if (!stbi__pic_is4(s,\"\\x53\\x80\\xF6\\x34\")) {\n      stbi__rewind(s);\n      return 0;\n   }\n\n   stbi__skip(s, 88);\n\n   *x = stbi__get16be(s);\n   *y = stbi__get16be(s);\n   if (stbi__at_eof(s)) {\n      stbi__rewind( s);\n      return 0;\n   }\n   if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {\n      stbi__rewind( s );\n      return 0;\n   }\n\n   stbi__skip(s, 8);\n\n   do {\n      stbi__pic_packet *packet;\n\n      if (num_packets==sizeof(packets)/sizeof(packets[0]))\n         return 0;\n\n      packet = &packets[num_packets++];\n      chained = stbi__get8(s);\n      packet->size    = stbi__get8(s);\n      packet->type    = stbi__get8(s);\n      packet->channel = stbi__get8(s);\n      act_comp |= packet->channel;\n\n      if (stbi__at_eof(s)) {\n          stbi__rewind( s );\n          return 0;\n      }\n      if (packet->size != 8) {\n          stbi__rewind( s );\n          return 0;\n      }\n   } while (chained);\n\n   *comp = (act_comp & 0x10 ? 4 : 3);\n\n   return 1;\n}\n#endif\n\n// *************************************************************************************************\n// Portable Gray Map and Portable Pixel Map loader\n// by Ken Miller\n//\n// PGM: http://netpbm.sourceforge.net/doc/pgm.html\n// PPM: http://netpbm.sourceforge.net/doc/ppm.html\n//\n// Known limitations:\n//    Does not support comments in the header section\n//    Does not support ASCII image data (formats P2 and P3)\n\n#ifndef STBI_NO_PNM\n\nstatic int      stbi__pnm_test(stbi__context *s)\n{\n   char p, t;\n   p = (char) stbi__get8(s);\n   t = (char) stbi__get8(s);\n   if (p != 'P' || (t != '5' && t != '6')) {\n       stbi__rewind( s );\n       return 0;\n   }\n   return 1;\n}\n\nstatic void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)\n{\n   stbi_uc *out;\n   STBI_NOTUSED(ri);\n\n   ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n);\n   if (ri->bits_per_channel == 0)\n      return 0;\n\n   if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n   if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc(\"too large\",\"Very large image (corrupt?)\");\n\n   *x = s->img_x;\n   *y = s->img_y;\n   if (comp) *comp = s->img_n;\n\n   if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0))\n      return stbi__errpuc(\"too large\", \"PNM too large\");\n\n   out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);\n   if (!out) return stbi__errpuc(\"outofmem\", \"Out of memory\");\n   if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {\n      STBI_FREE(out);\n      return stbi__errpuc(\"bad PNM\", \"PNM file truncated\");\n   }\n\n   if (req_comp && req_comp != s->img_n) {\n      if (ri->bits_per_channel == 16) {\n         out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);\n      } else {\n         out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);\n      }\n      if (out == NULL) return out; // stbi__convert_format frees input on failure\n   }\n   return out;\n}\n\nstatic int      stbi__pnm_isspace(char c)\n{\n   return c == ' ' || c == '\\t' || c == '\\n' || c == '\\v' || c == '\\f' || c == '\\r';\n}\n\nstatic void     stbi__pnm_skip_whitespace(stbi__context *s, char *c)\n{\n   for (;;) {\n      while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))\n         *c = (char) stbi__get8(s);\n\n      if (stbi__at_eof(s) || *c != '#')\n         break;\n\n      while (!stbi__at_eof(s) && *c != '\\n' && *c != '\\r' )\n         *c = (char) stbi__get8(s);\n   }\n}\n\nstatic int      stbi__pnm_isdigit(char c)\n{\n   return c >= '0' && c <= '9';\n}\n\nstatic int      stbi__pnm_getinteger(stbi__context *s, char *c)\n{\n   int value = 0;\n\n   while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {\n      value = value*10 + (*c - '0');\n      *c = (char) stbi__get8(s);\n      if((value > 214748364) || (value == 214748364 && *c > '7'))\n          return stbi__err(\"integer parse overflow\", \"Parsing an integer in the PPM header overflowed a 32-bit int\");\n   }\n\n   return value;\n}\n\nstatic int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)\n{\n   int maxv, dummy;\n   char c, p, t;\n\n   if (!x) x = &dummy;\n   if (!y) y = &dummy;\n   if (!comp) comp = &dummy;\n\n   stbi__rewind(s);\n\n   // Get identifier\n   p = (char) stbi__get8(s);\n   t = (char) stbi__get8(s);\n   if (p != 'P' || (t != '5' && t != '6')) {\n       stbi__rewind(s);\n       return 0;\n   }\n\n   *comp = (t == '6') ? 3 : 1;  // '5' is 1-component .pgm; '6' is 3-component .ppm\n\n   c = (char) stbi__get8(s);\n   stbi__pnm_skip_whitespace(s, &c);\n\n   *x = stbi__pnm_getinteger(s, &c); // read width\n   if(*x == 0)\n       return stbi__err(\"invalid width\", \"PPM image header had zero or overflowing width\");\n   stbi__pnm_skip_whitespace(s, &c);\n\n   *y = stbi__pnm_getinteger(s, &c); // read height\n   if (*y == 0)\n       return stbi__err(\"invalid width\", \"PPM image header had zero or overflowing width\");\n   stbi__pnm_skip_whitespace(s, &c);\n\n   maxv = stbi__pnm_getinteger(s, &c);  // read max value\n   if (maxv > 65535)\n      return stbi__err(\"max value > 65535\", \"PPM image supports only 8-bit and 16-bit images\");\n   else if (maxv > 255)\n      return 16;\n   else\n      return 8;\n}\n\nstatic int stbi__pnm_is16(stbi__context *s)\n{\n   if (stbi__pnm_info(s, NULL, NULL, NULL) == 16)\n\t   return 1;\n   return 0;\n}\n#endif\n\nstatic int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)\n{\n   #ifndef STBI_NO_JPEG\n   if (stbi__jpeg_info(s, x, y, comp)) return 1;\n   #endif\n\n   #ifndef STBI_NO_PNG\n   if (stbi__png_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_GIF\n   if (stbi__gif_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_BMP\n   if (stbi__bmp_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PIC\n   if (stbi__pic_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PNM\n   if (stbi__pnm_info(s, x, y, comp))  return 1;\n   #endif\n\n   #ifndef STBI_NO_HDR\n   if (stbi__hdr_info(s, x, y, comp))  return 1;\n   #endif\n\n   // test tga last because it's a crappy test!\n   #ifndef STBI_NO_TGA\n   if (stbi__tga_info(s, x, y, comp))\n       return 1;\n   #endif\n   return stbi__err(\"unknown image type\", \"Image not of any known type, or corrupt\");\n}\n\nstatic int stbi__is_16_main(stbi__context *s)\n{\n   #ifndef STBI_NO_PNG\n   if (stbi__png_is16(s))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PSD\n   if (stbi__psd_is16(s))  return 1;\n   #endif\n\n   #ifndef STBI_NO_PNM\n   if (stbi__pnm_is16(s))  return 1;\n   #endif\n   return 0;\n}\n\n#ifndef STBI_NO_STDIO\nSTBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)\n{\n    FILE *f = stbi__fopen(filename, \"rb\");\n    int result;\n    if (!f) return stbi__err(\"can't fopen\", \"Unable to open file\");\n    result = stbi_info_from_file(f, x, y, comp);\n    fclose(f);\n    return result;\n}\n\nSTBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)\n{\n   int r;\n   stbi__context s;\n   long pos = ftell(f);\n   stbi__start_file(&s, f);\n   r = stbi__info_main(&s,x,y,comp);\n   fseek(f,pos,SEEK_SET);\n   return r;\n}\n\nSTBIDEF int stbi_is_16_bit(char const *filename)\n{\n    FILE *f = stbi__fopen(filename, \"rb\");\n    int result;\n    if (!f) return stbi__err(\"can't fopen\", \"Unable to open file\");\n    result = stbi_is_16_bit_from_file(f);\n    fclose(f);\n    return result;\n}\n\nSTBIDEF int stbi_is_16_bit_from_file(FILE *f)\n{\n   int r;\n   stbi__context s;\n   long pos = ftell(f);\n   stbi__start_file(&s, f);\n   r = stbi__is_16_main(&s);\n   fseek(f,pos,SEEK_SET);\n   return r;\n}\n#endif // !STBI_NO_STDIO\n\nSTBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__info_main(&s,x,y,comp);\n}\n\nSTBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);\n   return stbi__info_main(&s,x,y,comp);\n}\n\nSTBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)\n{\n   stbi__context s;\n   stbi__start_mem(&s,buffer,len);\n   return stbi__is_16_main(&s);\n}\n\nSTBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)\n{\n   stbi__context s;\n   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);\n   return stbi__is_16_main(&s);\n}\n\n#endif // STB_IMAGE_IMPLEMENTATION\n\n/*\n   revision history:\n      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs\n      2.19  (2018-02-11) fix warning\n      2.18  (2018-01-30) fix warnings\n      2.17  (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug\n                         1-bit BMP\n                         *_is_16_bit api\n                         avoid warnings\n      2.16  (2017-07-23) all functions have 16-bit variants;\n                         STBI_NO_STDIO works again;\n                         compilation fixes;\n                         fix rounding in unpremultiply;\n                         optimize vertical flip;\n                         disable raw_len validation;\n                         documentation fixes\n      2.15  (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;\n                         warning fixes; disable run-time SSE detection on gcc;\n                         uniform handling of optional \"return\" values;\n                         thread-safe initialization of zlib tables\n      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs\n      2.13  (2016-11-29) add 16-bit API, only supported for PNG right now\n      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes\n      2.11  (2016-04-02) allocate large structures on the stack\n                         remove white matting for transparent PSD\n                         fix reported channel count for PNG & BMP\n                         re-enable SSE2 in non-gcc 64-bit\n                         support RGB-formatted JPEG\n                         read 16-bit PNGs (only as 8-bit)\n      2.10  (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED\n      2.09  (2016-01-16) allow comments in PNM files\n                         16-bit-per-pixel TGA (not bit-per-component)\n                         info() for TGA could break due to .hdr handling\n                         info() for BMP to shares code instead of sloppy parse\n                         can use STBI_REALLOC_SIZED if allocator doesn't support realloc\n                         code cleanup\n      2.08  (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA\n      2.07  (2015-09-13) fix compiler warnings\n                         partial animated GIF support\n                         limited 16-bpc PSD support\n                         #ifdef unused functions\n                         bug with < 92 byte PIC,PNM,HDR,TGA\n      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value\n      2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning\n      2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit\n      2.03  (2015-04-12) extra corruption checking (mmozeiko)\n                         stbi_set_flip_vertically_on_load (nguillemot)\n                         fix NEON support; fix mingw support\n      2.02  (2015-01-19) fix incorrect assert, fix warning\n      2.01  (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2\n      2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG\n      2.00  (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)\n                         progressive JPEG (stb)\n                         PGM/PPM support (Ken Miller)\n                         STBI_MALLOC,STBI_REALLOC,STBI_FREE\n                         GIF bugfix -- seemingly never worked\n                         STBI_NO_*, STBI_ONLY_*\n      1.48  (2014-12-14) fix incorrectly-named assert()\n      1.47  (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)\n                         optimize PNG (ryg)\n                         fix bug in interlaced PNG with user-specified channel count (stb)\n      1.46  (2014-08-26)\n              fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG\n      1.45  (2014-08-16)\n              fix MSVC-ARM internal compiler error by wrapping malloc\n      1.44  (2014-08-07)\n              various warning fixes from Ronny Chevalier\n      1.43  (2014-07-15)\n              fix MSVC-only compiler problem in code changed in 1.42\n      1.42  (2014-07-09)\n              don't define _CRT_SECURE_NO_WARNINGS (affects user code)\n              fixes to stbi__cleanup_jpeg path\n              added STBI_ASSERT to avoid requiring assert.h\n      1.41  (2014-06-25)\n              fix search&replace from 1.36 that messed up comments/error messages\n      1.40  (2014-06-22)\n              fix gcc struct-initialization warning\n      1.39  (2014-06-15)\n              fix to TGA optimization when req_comp != number of components in TGA;\n              fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)\n              add support for BMP version 5 (more ignored fields)\n      1.38  (2014-06-06)\n              suppress MSVC warnings on integer casts truncating values\n              fix accidental rename of 'skip' field of I/O\n      1.37  (2014-06-04)\n              remove duplicate typedef\n      1.36  (2014-06-03)\n              convert to header file single-file library\n              if de-iphone isn't set, load iphone images color-swapped instead of returning NULL\n      1.35  (2014-05-27)\n              various warnings\n              fix broken STBI_SIMD path\n              fix bug where stbi_load_from_file no longer left file pointer in correct place\n              fix broken non-easy path for 32-bit BMP (possibly never used)\n              TGA optimization by Arseny Kapoulkine\n      1.34  (unknown)\n              use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case\n      1.33  (2011-07-14)\n              make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements\n      1.32  (2011-07-13)\n              support for \"info\" function for all supported filetypes (SpartanJ)\n      1.31  (2011-06-20)\n              a few more leak fixes, bug in PNG handling (SpartanJ)\n      1.30  (2011-06-11)\n              added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)\n              removed deprecated format-specific test/load functions\n              removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway\n              error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)\n              fix inefficiency in decoding 32-bit BMP (David Woo)\n      1.29  (2010-08-16)\n              various warning fixes from Aurelien Pocheville\n      1.28  (2010-08-01)\n              fix bug in GIF palette transparency (SpartanJ)\n      1.27  (2010-08-01)\n              cast-to-stbi_uc to fix warnings\n      1.26  (2010-07-24)\n              fix bug in file buffering for PNG reported by SpartanJ\n      1.25  (2010-07-17)\n              refix trans_data warning (Won Chun)\n      1.24  (2010-07-12)\n              perf improvements reading from files on platforms with lock-heavy fgetc()\n              minor perf improvements for jpeg\n              deprecated type-specific functions so we'll get feedback if they're needed\n              attempt to fix trans_data warning (Won Chun)\n      1.23    fixed bug in iPhone support\n      1.22  (2010-07-10)\n              removed image *writing* support\n              stbi_info support from Jetro Lauha\n              GIF support from Jean-Marc Lienher\n              iPhone PNG-extensions from James Brown\n              warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)\n      1.21    fix use of 'stbi_uc' in header (reported by jon blow)\n      1.20    added support for Softimage PIC, by Tom Seddon\n      1.19    bug in interlaced PNG corruption check (found by ryg)\n      1.18  (2008-08-02)\n              fix a threading bug (local mutable static)\n      1.17    support interlaced PNG\n      1.16    major bugfix - stbi__convert_format converted one too many pixels\n      1.15    initialize some fields for thread safety\n      1.14    fix threadsafe conversion bug\n              header-file-only version (#define STBI_HEADER_FILE_ONLY before including)\n      1.13    threadsafe\n      1.12    const qualifiers in the API\n      1.11    Support installable IDCT, colorspace conversion routines\n      1.10    Fixes for 64-bit (don't use \"unsigned long\")\n              optimized upsampling by Fabian \"ryg\" Giesen\n      1.09    Fix format-conversion for PSD code (bad global variables!)\n      1.08    Thatcher Ulrich's PSD code integrated by Nicolas Schulz\n      1.07    attempt to fix C++ warning/errors again\n      1.06    attempt to fix C++ warning/errors again\n      1.05    fix TGA loading to return correct *comp and use good luminance calc\n      1.04    default float alpha is 1, not 255; use 'void *' for stbi_image_free\n      1.03    bugfixes to STBI_NO_STDIO, STBI_NO_HDR\n      1.02    support for (subset of) HDR files, float interface for preferred access to them\n      1.01    fix bug: possible bug in handling right-side up bmps... not sure\n              fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all\n      1.00    interface to zlib that skips zlib header\n      0.99    correct handling of alpha in palette\n      0.98    TGA loader by lonesock; dynamically add loaders (untested)\n      0.97    jpeg errors on too large a file; also catch another malloc failure\n      0.96    fix detection of invalid v value - particleman@mollyrocket forum\n      0.95    during header scan, seek to markers in case of padding\n      0.94    STBI_NO_STDIO to disable stdio usage; rename all #defines the same\n      0.93    handle jpegtran output; verbose errors\n      0.92    read 4,8,16,24,32-bit BMP files of several formats\n      0.91    output 24-bit Windows 3.0 BMP files\n      0.90    fix a few more warnings; bump version number to approach 1.0\n      0.61    bugfixes due to Marc LeBlanc, Christopher Lloyd\n      0.60    fix compiling as c++\n      0.59    fix warnings: merge Dave Moore's -Wall fixes\n      0.58    fix bug: zlib uncompressed mode len/nlen was wrong endian\n      0.57    fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available\n      0.56    fix bug: zlib uncompressed mode len vs. nlen\n      0.55    fix bug: restart_interval not initialized to 0\n      0.54    allow NULL for 'int *comp'\n      0.53    fix bug in png 3->4; speedup png decoding\n      0.52    png handles req_comp=3,4 directly; minor cleanup; jpeg comments\n      0.51    obey req_comp requests, 1-component jpegs return as 1-component,\n              on 'test' only check type, not whether we support this variant\n      0.50  (2006-11-19)\n              first released version\n*/\n\n\n/*\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "src/samples/ui.c",
    "content": "#include \"single_file_lib/rt/rt.h\"\n#define ui_implementation\n#include \"single_file_lib/ui/ui.h\"\n"
  },
  {
    "path": "src/tools/amalgamate.c",
    "content": "#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"dirent.h\"\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#pragma GCC diagnostic ignored \"-Wfour-char-constants\"\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#pragma GCC diagnostic ignored \"-Wmissing-noreturn\"\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"\n#pragma GCC diagnostic ignored \"-Wcast-align\"\n#pragma GCC diagnostic ignored \"-Waddress-of-packed-member\"\n#pragma GCC diagnostic ignored \"-Wused-but-marked-unused\" // because in debug only\n#endif\n\n#define null NULL\n\n#ifndef countof\n    #define rt_countof(a) ((int)(sizeof(a) / sizeof((a)[0])))\n#endif\n\n#define strequ(s1, s2) (strcmp((s1), (s2)) == 0)\n\nstatic const char* exe;\nstatic const char* name;\nstatic int32_t strlen_name;\nstatic char inc[256];\nstatic char src[256];\nstatic char mem[1024 * 1023];\nstatic char* brk = mem;\n\n#define rt_fatal_if(x, ...) do {                                   \\\n    if (x) {                                                    \\\n        fprintf(stderr, \"%s:%d: %s\\n\", __FILE__, __LINE__, #x); \\\n        fprintf(stderr, \"\" __VA_ARGS__);                        \\\n        fprintf(stderr, \"\\nFATAL\\n\");                           \\\n        exit(1);                                                \\\n    }                                                           \\\n} while (0)\n\nstatic const char* basename(const char* filename) {\n    const char* s = strrchr(filename, '\\\\');\n    if (s == null) { s = strrchr(filename, '/'); }\n    return s != null ? s + 1 : filename;\n}\n\nstatic char* dup(const char* s) { // strdup() like to avoid leaks reporting\n    int n = (int)strlen(s) + 1;\n    rt_fatal_if(brk + n > mem + sizeof(mem), \"out of memory\");\n    char* c = (char*)memcpy(brk, s, (size_t)n);\n    brk += n;\n    return c;\n}\n\nstatic char* concat(const char* s1, const char* s2) {\n    int n1 = (int)strlen(s1);\n    int n2 = (int)strlen(s2);\n    rt_fatal_if(brk + n1 + n2 + 1 > mem + sizeof(mem), \"out of memory\");\n    char* c = brk;\n    memcpy((char*)memcpy(brk, s1, (size_t)n1) + n1, s2, (size_t)n2 + 1);\n    brk += n1 + n2 + 1;\n    return c;\n}\n\nstatic bool ends_with(const char* s1, const char* s2) {\n    int32_t n1 = (int)strlen(s1);\n    int32_t n2 = (int)strlen(s2);\n    return n1 >= n2 && strequ(s1 + n1 - n2, s2);\n}\n\ntypedef struct { const char* a[1024]; int32_t n; } set_t;\n\nstatic set_t files;\nstatic set_t includes;\n\nstatic bool set_has(set_t* set, const char* s) {\n    for (int32_t i = 0; i < set->n; i++) {\n        if (strequ(set->a[i], s)) { return true; }\n    }\n    return false;\n}\n\nstatic void set_add(set_t* set, const char* s) {\n    assert(!set_has(set, s));\n    if (!set_has(set, s)) {\n        rt_fatal_if(set->n == rt_countof(set->a), \"too many files\");\n        set->a[set->n] = dup(s);\n        set->n++;\n    }\n}\n\nstatic int32_t usage(void) {\n    fprintf(stderr, \"\\n\");\n    fprintf(stderr, \"Usage:\\n\");\n    fprintf(stderr, \"%s <name>\\n\", exe);\n    fprintf(stderr, \"Assumes src/name and inc/name folders exist\\n\");\n    fprintf(stderr, \"and inc/<name>/ contain <name>.h\\n\");\n    fprintf(stderr, \"\\n\");\n    return 1;\n}\n\nstatic void tail_trim(char* s) {\n    char* p = s + strlen(s) - 1;\n    while (p >= s && *p < 0x20) { *p-- = 0x00; }\n}\n\nstatic void divider(const char* fn) {\n    char underscores[40] = {0};\n    memset(underscores, '_', rt_countof(underscores) - 1);\n    int32_t i = (int)(74 - strlen(fn)) / 2;\n    int32_t j = (int)(74 - i - (int)strlen(fn));\n    printf(\"// %.*s %s %.*s\\n\\n\", i, underscores, fn, j, underscores);\n}\n\nstatic const char* include(char* s) {\n    char fn[256] = {0};\n    const char* include = \"#include\\x20\\\"\";\n    if (strstr(s, include) == s) {\n        s += strlen(include);\n        const char* q = strchr(s, '\"');\n        if (q != null) {\n            snprintf(fn, rt_countof(fn) - 1, \"%.*s\", (int)(q - s), s);\n            return strstr(fn, name) == fn && fn[strlen_name] == '/' ?\n                   dup(fn + strlen_name + 1) : null;\n        }\n    }\n    return null;\n}\n\n\nstatic bool already_included(const char* s) {\n    const char* include = \"#include\\x20\\\"\";\n    if (strstr(s, include) != s) { return false; }\n    if (set_has(&includes, s)) { return true; }\n    set_add(&includes, s);\n    return false;\n}\n\nstatic bool ignore(const char* s) {\n    return strequ(s, \"#pragma once\") || already_included(s);\n}\n\nstatic void parse(const char* fn) {\n    FILE* f = fopen(fn, \"r\");\n    rt_fatal_if(f == null, \"file not found: `%s`\", fn);\n    static char line[16 * 1024];\n    bool first = true;\n    while (fgets(line, rt_countof(line) - 1, f) != null) {\n        tail_trim(line);\n        const char* in = include(line);\n        if (in != null) {\n            if (!set_has(&files, in)) {\n                set_add(&files, in);\n                parse(concat(inc, concat(\"/\", in)));\n            }\n        } else if (ends_with(fn, \".c\") || !ignore(line)) {\n            if (first && line[0] != 0) {\n                divider(fn + 5 + strlen_name); first = false;\n            }\n            printf(\"%s\\n\", line);\n        }\n    }\n    fclose(f);\n}\n\nstatic void definition(void) {\n    printf(\"#ifndef %s_definition\\n\", name);\n    printf(\"#define %s_definition\\n\", name);\n    printf(\"\\n\");\n    parse(concat(inc, concat(\"/\", concat(name, \".h\"))));\n    printf(\"\\n\");\n    printf(\"#endif // %s_definition\\n\", name);\n    const char* name_h = concat(name, \".h\");\n    // because name.h is fully processed do not include it again:\n    if (!set_has(&files, name_h)) { set_add(&files, concat(name, \".h\")); }\n}\n\nstatic void implementation(void) {\n    printf(\"\\n\");\n    printf(\"#ifdef %s_implementation\\n\", name);\n    DIR* d = opendir(src);\n    rt_fatal_if(d == null, \"folder not found: `%s`\", src);\n    struct dirent* e = readdir(d);\n    while (e != null) {\n        if (ends_with(e->d_name, \".c\")) {\n            parse(concat(src, concat(\"/\", e->d_name)));\n        }\n        e = readdir(d);\n    }\n    rt_fatal_if(closedir(d) != 0);\n    printf(\"\\n\");\n    printf(\"#endif // %s_implementation\\n\", name);\n    printf(\"\\n\");\n}\n\nint main(int argc, const char* argv[]) {\n    exe = basename(argv[0]);\n    if (argc < 2) { exit(usage()); }\n    name = argv[1];\n    strlen_name = (int)strlen(name);\n    snprintf(inc, rt_countof(inc) - 1, \"inc/%s\", name);\n    snprintf(src, rt_countof(inc) - 1, \"src/%s\", name);\n    definition();\n    implementation();\n    return 0;\n}\n"
  },
  {
    "path": "src/tools/dirent.c",
    "content": "#include \"dirent.h\"\n#include <stdio.h>\n#include <malloc.h>\n#include <Windows.h>\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#endif\n\n#define null ((void*)0)\n\n#ifndef countof\n    #define rt_countof(a) ((int)(sizeof(a) / sizeof((a)[0])))\n#endif\n\ntypedef struct dir_s {\n    HANDLE handle;\n    WIN32_FIND_DATAA find; // 320 bytes\n    struct dirent entry;\n} dir_t;\n\nDIR *opendir(const char *dirname) {\n    dir_t *d = calloc(1, sizeof(dir_t));\n    if (d != null) {\n        char spec[NAME_MAX + 2]; // extra room for \"\\*\" suffix\n        snprintf(spec, rt_countof(spec) - 1, \"%s\\\\*\", dirname);\n        spec[rt_countof(spec) - 1] = 0;\n        d->handle = FindFirstFileA(spec, &d->find);\n        if (d->handle == INVALID_HANDLE_VALUE) {\n            free(d);\n            d = null;\n        }\n    }\n    return (DIR*)d;\n}\n\nstruct dirent* readdir(DIR* dir) {\n    dir_t* d = (dir_t*)dir;\n    struct dirent* de = null;\n    if (d->handle != INVALID_HANDLE_VALUE &&\n        FindNextFileA(d->handle, &d->find)) {\n        enum { n = rt_countof(d->entry.d_name) };\n        strncpy(d->entry.d_name, d->find.cFileName, n - 1);\n        d->entry.d_name[n - 1] = 0x00; // Ensure zero termination\n        de = &d->entry;\n    }\n    return de;\n}\n\nint closedir(DIR* dir) {\n    errno_t e = 0;\n    dir_t *d = (dir_t*)dir;\n    if (d->handle != INVALID_HANDLE_VALUE) {\n        if (!FindClose(d->handle)) { e = EINVAL; }\n    }\n    if (e == 0) { free(d); }\n    return e;\n}\n"
  },
  {
    "path": "src/tools/dirent.h",
    "content": "#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// https://pubs.opengroup.org/onlinepubs/009604599/basedefs/dirent.h.html\n\n#define NAME_MAX 260\n\ntypedef struct DIR DIR;\n\nstruct dirent { char d_name[NAME_MAX]; };\n\nDIR* opendir(const char *dirname);\nstruct dirent *readdir(DIR *dirp);\nint closedir(DIR* dir);\n\n#ifdef __cplusplus\n} // extern \"C\"\n#endif\n\n"
  },
  {
    "path": "src/tools/version.c",
    "content": "#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n\n#ifdef _MSC_FULL_VER\n#define rt_countof(a) _countof(a)\n#define popen(c, m) _popen(c, m)\n#define pclose(f) _pclose(f)\n#endif\n\n#if defined(__GNUC__) || defined(__clang__) // TODO: remove and fix code\n#pragma GCC diagnostic ignored \"-Wunsafe-buffer-usage\"\n#pragma GCC diagnostic ignored \"-Wdeclaration-after-statement\"\n#endif\n\nenum { max_command_output = 16 * 1024 };\n\nstatic errno_t run_command(const char* command, char* output, size_t max_output) {\n    FILE* f = popen(command, \"r\");\n    errno_t r = f != NULL ? 1 : -1;\n    size_t total = 0;\n    while (r > 0) {\n        size_t seen = fread(output + total, 1, max_output - total, f);\n        if (seen <= 0) {\n            r = 0;\n        } else if (total + seen + 1 >= max_output) {\n            r = -1;\n        } else {\n            total += seen;\n        }\n    }\n    if (f != NULL) { pclose(f); }\n    if (total < max_output) {\n        output[total] = 0;\n    }\n    return r;\n}\n\nint main(void) {\n    printf(\"// Automatically generated by version.c (project prebuild).\\n\");\n    printf(\"// DO NOT EDIT.\\n\");\n    printf(\"\\n\");\n    static char hash[max_command_output];\n    strcpy(hash, \"BADF00D\");\n    if (run_command(\"git rev-parse --short HEAD\", hash, rt_countof(hash)) != 0) {\n        fprintf(stderr, \"Failed to get git hash.\\n\");\n        return 1;\n    }\n    if (hash[strlen(hash) - 1] == '\\n') { hash[strlen(hash) - 1] = 0; }\n    time_t t = time(NULL);\n    struct tm* utc = gmtime(&t);\n    static char tag[max_command_output];\n    strcpy(tag, \"C0DEFEED\");\n    if (run_command(\"git describe --tags HEAD 2>nul\", tag, rt_countof(tag)) != 0) {\n        fprintf(stderr, \"Failed to get git tag.\\n\");\n        return 1;\n    }\n    if (tag[strlen(tag) - 1] == '\\n') { tag[strlen(tag) - 1] = 0; }\n    printf(\"#pragma once\\n\");\n    printf(\"#define version_hash \\\"%s\\\"\\n\", hash);\n    printf(\"#define version_tag \\\"%s\\\"\\n\", tag);\n    printf(\"#define version_yy (%02d)\\n\", utc->tm_year % 100);\n    printf(\"#define version_mm (%02d)\\n\", utc->tm_mon + 1);\n    printf(\"#define version_dd (%02d)\\n\", utc->tm_mday);\n    printf(\"#define version_hh (%02d)\\n\", utc->tm_hour);\n    printf(\"#define version_str \\\"%02d.%02d.%02d.%02dUTC %s\\\"\\n\",\n        utc->tm_year % 100, utc->tm_mon + 1, utc->tm_mday, utc->tm_hour, hash);\n    printf(\"#define version_int32 (0x%02d%02d%02d%02d)\\n\",\n        utc->tm_year % 100, utc->tm_mon + 1, utc->tm_mday, utc->tm_hour);\n    printf(\"#define version_hash_int64 (0x%sLL)\\n\", hash);\n    return 0;\n}\n"
  },
  {
    "path": "src/ui/attic/ui_theme.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"ut/ut.h\"\n#include \"ui/ui.h\"\n#include \"ui/ut_win32.h\"\n\n#pragma push_macro(\"ux_theme_reg_cv\")\n#pragma push_macro(\"ux_theme_reg_default_colors\")\n\n#define ux_theme_reg_cv \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\\"\n#define ux_theme_reg_default_colors ux_theme_reg_cv \"Themes\\\\DefaultColors\\\\\"\n\nstatic HMODULE ui_theme_ux_theme(void) {\n    static HMODULE ux_theme;\n    if (ux_theme == null) {\n        ux_theme = GetModuleHandleA(\"uxtheme.dll\");\n    }\n    if (ux_theme == null) {\n        ux_theme = (HMODULE)ut_loader.open(\"uxtheme.dll\", ut_loader.local);\n    }\n    not_null(ux_theme);\n    return ux_theme;\n}\n\nstatic errno_t ui_theme_reg_get_uint32(HKEY root, const char* path,\n        const char* key, DWORD *v) {\n    *v = 0;\n    DWORD type = REG_DWORD;\n    DWORD light_theme = 0;\n    DWORD bytes = sizeof(light_theme);\n    errno_t r = RegGetValueA(root, path, key, RRF_RT_DWORD, &type, v, &bytes);\n    if (r != 0) {\n        traceln(\"RegGetValueA(%s\\\\%s) failed %s\", path, key, ut_str.error(r));\n    }\n    return r;\n}\n\nstatic errno_t ui_theme_reg_get_bin(HKEY root, const char* path,\n        const char* key, void *data, uint32_t *bytes) {\n    memset(data, 0, *bytes);\n    DWORD type = REG_BINARY;\n    DWORD n = *bytes;\n    errno_t r = RegGetValueA(root, path, key, RRF_RT_REG_BINARY, &type, data, &n);\n    if (r == 0) { *bytes = n; }\n    if (r != 0) {\n        traceln(\"RegGetValueA(%s\\\\%s) failed %s\", path, key, ut_str.error(r));\n    }\n    return r;\n}\n\ntypedef struct {\n    int32_t id;\n    const char* name;\n    ui_color_t  dark;\n    ui_color_t  light;\n} ui_theme_color_map_t;\n\n// ActiveTitle         : dark 0x006E0037 light 0x00D1B499\n// ButtonFace          : dark 0x00000000 light 0x00F0F0F0\n// ButtonText          : dark 0x00FFFFFF light 0x00000000\n// GrayText            : dark 0x003FF23F light 0x006D6D6D\n// Hilight             : dark 0x00FFEB1A light 0x00D77800\n// HilightText         : dark 0x00000000 light 0x00FFFFFF\n// HotTrackingColor    : dark 0x0000FFFF light 0x00CC6600\n// InactiveTitle       : dark 0x002F0000 light 0x00DBCDBF\n// InactiveTitleText   : dark 0x00FFFFFF light 0x00000000\n// MenuHilight         : dark 0x00800080 light 0x00FF9933\n// TitleText           : dark 0x00FFFFFF light 0x00000000\n// Window              : dark 0x00000000 light 0x00FFFFFF\n// WindowText          : dark 0x00FFFFFF light 0x00000000\n\nstatic int32_t ui_theme_dark = -1; // -1 unknown\n\nstatic ui_theme_color_map_t ui_theme_colors[13];\n\nstatic void ui_theme_init_colors(void) {\n    // this is empirically determined for the dark theme, and from Win10\n    // the registry in standard light theme.\n    // Because all of the:\n    // HKEY_CURRENT_USER\\Control Panel\\Desktop\\Colors\n    // HKEY_CURRENT_USER\\Control Panel\\Colors\n    // HKEY_USERS\\.DEFAULT\\Control Panel\\Colors\n    // HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\DefaultColors\\Standard\n    // Have different colors even for Light mode and none for the Dark Mode simply\n    // keep it hardcoded for time being:\n    ui_theme_colors[ 0] = (ui_theme_color_map_t){ .id = ui.colors.active_title,        .name = \"ActiveTitle\"      ,.dark = 0x00000000, .light = 0x00D1B499 };\n    ui_theme_colors[ 1] = (ui_theme_color_map_t){ .id = ui.colors.button_face,         .name = \"ButtonFace\"       ,.dark = 0x00333333, .light = 0x00F0F0F0 };\n    ui_theme_colors[ 2] = (ui_theme_color_map_t){ .id = ui.colors.button_text,         .name = \"ButtonText\"       ,.dark = 0x00FFFFFF, .light = 0x00000000 };\n    ui_theme_colors[ 3] = (ui_theme_color_map_t){ .id = ui.colors.gray_text,           .name = \"GrayText\"         ,.dark = 0x00666666, .light = 0x006D6D6D };\n    ui_theme_colors[ 4] = (ui_theme_color_map_t){ .id = ui.colors.highlight,           .name = \"Hilight\"          ,.dark = 0x00626262, .light = 0x00D77800 };\n    ui_theme_colors[ 5] = (ui_theme_color_map_t){ .id = ui.colors.highlight_text,      .name = \"HilightText\"      ,.dark = 0x00000000, .light = 0x00FFFFFF };\n    ui_theme_colors[ 6] = (ui_theme_color_map_t){ .id = ui.colors.hot_tracking_color,  .name = \"HotTrackingColor\" ,.dark = 0x004D8DFA, .light = 0x00CC6600 };\n    ui_theme_colors[ 7] = (ui_theme_color_map_t){ .id = ui.colors.inactive_title,      .name = \"InactiveTitle\"    ,.dark = 0x002B2B2B, .light = 0x00DBCDBF };\n    ui_theme_colors[ 8] = (ui_theme_color_map_t){ .id = ui.colors.inactive_title_text, .name = \"InactiveTitleText\",.dark = 0x00969696, .light = 0x00000000 };\n    ui_theme_colors[ 9] = (ui_theme_color_map_t){ .id = ui.colors.menu_highlight,      .name = \"MenuHilight\"      ,.dark = 0x00002642, .light = 0x00FF9933 };\n    ui_theme_colors[10] = (ui_theme_color_map_t){ .id =  ui.colors.title_text,         .name = \"TitleText\"        ,.dark = 0x00FFFFFF, .light = 0x00000000 };\n    ui_theme_colors[11] = (ui_theme_color_map_t){ .id =  ui.colors.window,             .name = \"Window\"           ,.dark = 0x00000000, .light = 0x00FFFFFF };\n    ui_theme_colors[12] = (ui_theme_color_map_t){ .id =  ui.colors.window_text,        .name = \"WindowText\"       ,.dark = 0x00FFFFFF, .light = 0x00000000 };\n    #ifdef UX_THEME_READ_COLORS_FROM_REGISTRY // when the dust of Win11 settles.\n    errno_t r = 0;\n    const char* dark  = ux_theme_reg_default_colors \"HighContrast\";\n    const char* light = ux_theme_reg_default_colors \"Standard\";\n    for (int32_t i = 0; i < countof(ui_theme_colors); i++) {\n        const char* name = ui_theme_colors[i].name;\n        DWORD dc = 0;\n        DWORD lc = 0;\n        r = ui_theme_reg_get_uint32(HKEY_LOCAL_MACHINE, dark,  name, &dc);\n        if (r == 0) { ui_theme_colors[i].dark = dc; }\n        ui_theme_reg_get_uint32(HKEY_LOCAL_MACHINE, light, name, &lc);\n        if (r == 0) { ui_theme_colors[i].light = lc; }\n//      traceln(\"%-20s: dark %08X light %08X\", name, dc, lc);\n    }\n    #endif\n}\n\nstatic bool ui_theme_use_light_theme(const char* key) {\n    const char* personalize  = ux_theme_reg_cv \"Themes\\\\Personalize\";\n    DWORD light_theme = 0;\n    ui_theme_reg_get_uint32(HKEY_CURRENT_USER, personalize, key, &light_theme);\n    return light_theme != 0;\n}\n\nstatic bool ui_theme_are_apps_light(void) {\n    return ui_theme_use_light_theme(\"AppsUseLightTheme\");\n}\n\nstatic bool ui_theme_is_system_light(void) {\n    return ui_theme_use_light_theme(\"SystemUsesLightTheme\");\n}\n\nstatic ui_color_t ui_theme_get_color(int32_t color_id) {\n    swear(0 <= color_id && color_id < countof(ui_theme_colors));\n    static bool initialized;\n    if (!initialized) { ui_theme_init_colors(); initialized = true; }\n    if (ui_theme_dark < 0) {\n        bool are_apps_light = ui_theme.are_apps_light();\n        bool is_system_light  = ui_theme.is_system_light();\n        bool allowed  = ui_theme.is_dark_mode_allowed_for_app();\n        bool dark  = ui_theme.should_apps_use_dark_mode();\n        ui_theme_dark = !is_system_light && !are_apps_light && allowed && dark;\n        if (ui_theme_dark) {\n            ui_theme.set_preferred_app_mode(ui_theme.mode_force_dark);\n        }\n    }\n    return ui_theme_dark ? ui_theme_colors[color_id].dark :\n                           ui_theme_colors[color_id].light;\n}\n\nstatic void ui_theme_refresh(void* window) {\n    ui_theme_dark = -1;\n    BOOL dark_mode = !ui_theme.are_apps_light();\n    static const DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20;\n    /* 20 == DWMWA_USE_IMMERSIVE_DARK_MODE in Windows 11 SDK.\n       This value was undocumented for Windows 10 versions 2004\n       and later, supported for Windows 11 Build 22000 and later. */\n    errno_t r = DwmSetWindowAttribute((HWND)window,\n        DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_mode, sizeof(dark_mode));\n    if (r != 0) {\n        traceln(\"DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) \"\n                \"failed %s\", ut_str.error(r));\n    }\n    ui_app.layout();\n}\n\nstatic bool ui_theme_is_dark_mode_allowed_for_app(void) {\n    typedef BOOL (__stdcall *IsDarkModeAllowedForApp_t)(void);\n    IsDarkModeAllowedForApp_t IsDarkModeAllowedForApp = (IsDarkModeAllowedForApp_t)\n            (void*)GetProcAddress(ui_theme_ux_theme(), MAKEINTRESOURCE(136));\n    if (IsDarkModeAllowedForApp != null) {\n        return IsDarkModeAllowedForApp();\n    }\n    return false;\n}\n\nstatic bool ui_theme_should_apps_use_dark_mode(void) {\n    typedef BOOL (__stdcall *ShouldAppsUseDarkMode_t)(void);\n    ShouldAppsUseDarkMode_t ShouldAppsUseDarkMode = (ShouldAppsUseDarkMode_t)\n            (void*)GetProcAddress(ui_theme_ux_theme(), MAKEINTRESOURCE(132));\n    if (ShouldAppsUseDarkMode != null) {\n        return ShouldAppsUseDarkMode();\n    }\n    return false;\n}\n\nstatic void ui_theme_set_preferred_app_mode(int32_t mode) {\n    typedef BOOL (__stdcall *SetPreferredAppMode_t)(bool allow);\n    SetPreferredAppMode_t SetPreferredAppMode = (SetPreferredAppMode_t)\n            (void*)GetProcAddress(ui_theme_ux_theme(), MAKEINTRESOURCE(135));\n    if (SetPreferredAppMode != null) {\n        errno_t r = b2e(SetPreferredAppMode(mode));\n        // fails on Windows 10 with: ERROR_RESOURCE_NAME_NOT_FOUND (1814)\n        if (r != 0 && r != ERROR_RESOURCE_NAME_NOT_FOUND) { // ignore\n            traceln(\"SetPreferredAppMode(%d) failed %s\", mode, ut_str.error(r));\n        }\n    }\n}\n\nstatic ui_color_t ui_theme_explorer_accents[] = {\n    0x00FFD8A6, 0x00EDB976, 0x00E39C42, 0x00D77800,\n    0x009E5A00, 0x00754200, 0x00422600, 0x00981788\n};\n\nstatic void ui_theme_test(void) {\n    DWORD window = GetSysColor(COLOR_WINDOW);\n    DWORD text   = GetSysColor(COLOR_WINDOWTEXT);\n    traceln(\"COLOR_WINDOW: 0x%08X COLOR_WINDOWTEXT: 0x%08X\", window, text);\n    DWORD colors[8] = {0};\n    uint32_t bytes = sizeof(colors);\n    ui_theme_reg_get_bin(HKEY_CURRENT_USER,\n        ux_theme_reg_cv \"Explorer\\\\Accent\",\n        \"AccentPalette\", &colors, &bytes);\n    ui_theme_init_colors();\n    HMODULE ux_theme = ui_theme_ux_theme();\n    traceln(\"ux_theme: %p\", ux_theme);\n    bool are_apps_light = ui_theme.are_apps_light();\n    bool is_system_light  = ui_theme.is_system_light();\n    bool dark  = ui_theme.should_apps_use_dark_mode();\n    bool allowed  = ui_theme.is_dark_mode_allowed_for_app();\n    traceln(\"light is_system_light(): %d are_apps_light(): %d \"\n            \"should_apps_use_dark_mode(): %d \"\n            \"is_dark_mode_allowed_for_app(): %d\",\n            is_system_light, are_apps_light, dark, allowed);\n    if (dark) {\n        ui_theme.set_preferred_app_mode(ui_theme.mode_force_dark);\n    }\n    for (int32_t i = 0; i < countof(ui_theme_colors); i++) {\n        ui_color_t c = ui_theme.get_color(ui_theme_colors[i].id);\n        traceln(\"%-20s 0x%08X\", ui_theme_colors[i].name, ui_color_rgb(c));\n    }\n}\n\nui_theme_if ui_theme = {\n    .mode_default     = 0,\n    .mode_allow_dark  = 1,\n    .mode_force_dark  = 2,\n    .mode_force_light = 3,\n    .is_system_light              = ui_theme_is_system_light,\n    .are_apps_light               = ui_theme_are_apps_light,\n    .should_apps_use_dark_mode    = ui_theme_should_apps_use_dark_mode,\n    .is_dark_mode_allowed_for_app = ui_theme_is_dark_mode_allowed_for_app,\n    .set_preferred_app_mode       = ui_theme_set_preferred_app_mode,\n    .get_color                    = ui_theme_get_color,\n    .refresh                      = ui_theme_refresh,\n    .test                         = ui_theme_test\n};\n\nut_static_init(ui_theme) { ui_theme.test(); }\n\n// Experimental Dark:\n\n// ActiveTitle         : dark 0x00000000 ***\n// ButtonFace          : dark 0x00333333 ***\n// ButtonText          : dark 0x00FFFFFF ***\n// GrayText            : dark 0x00666666 ***\n// Hilight             : dark 0x00626262 ***\n// HilightText         : dark 0x00000000 ***\n// HotTrackingColor    : dark 0x004D8DFA ***  0x00D77800 is light\n// InactiveTitle       : dark 0x002B2B2B ***\n// InactiveTitleText   : dark 0x00969696 *** (alt A1A1A1 or AAAAAA)\n// MenuHilight         : dark 0x00002642 ***\n// TitleText           : dark 0x00FFFFFF ***\n// Window              : dark 0x00000000 ***\n// WindowText          : dark 0x00FFFFFF ***\n\n\n//\n//  Computer\\HKEY_CURRENT_USER\\Control Panel\\Desktop\\\n//  AutoColorization=1\n//\n//  Computer\\HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\DWM\n//  ColorizationColor=0xC43A3A3A // (alpha: C4!)\n//  EnableWindowColorization=0x84 // (bitset of something)\n//\n//  TODO: these values differ:\n//\n// Computer\\HKEY_CURRENT_USER\\Control Panel\\Desktop\\Colors\n// Computer\\HKEY_CURRENT_USER\\Control Panel\\Colors\n//\n//  TODO: may be relevant too:\n//  HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\DWM\\\n//  absent:\n//  AccentColorInactive\n//  present:\n//  AccentColor ff3a3a3a\n//  ColorizationAfterglow\n//  ColorizationColor\n//\n//  HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Accent\n//  AccentColorMenu\n//  StartColorMenu\n//  AccentPalette binary 8x4byte colors\n//\n// Computer\\HKEY_USERS\\.DEFAULT\\Control Panel\\Colors\n// a lot of colors\n\n// https://superuser.com/questions/1245923/registry-keys-to-change-personalization-settings/1395560#1395560\n// Active Window Border\n// [HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Accent]\n// \"AccentColorMenu\"=dword:ffb16300\n// Active Window Title Bar\n// [HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM]\n// \"AccentColor\"=dword:ffb16300\n// Inactive Window Title Bar\n// [HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM]\n// \"AccentColorInactive\"=dword:ffb16300\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n#pragma pop_macro(\"ux_theme_reg_cv\")\n#pragma pop_macro(\"ux_theme_reg_default_colors\")\n\n\n"
  },
  {
    "path": "src/ui/ui_app.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"rt/rt_win32.h\"\n\n#pragma push_macro(\"ui_app_window\")\n#pragma push_macro(\"ui_app_canvas\")\n\nstatic bool ui_app_trace_utf16_keyboard_input;\n\n#define ui_app_window() ((HWND)ui_app.window)\n#define ui_app_canvas() ((HDC)ui_app.canvas)\n\nstatic WNDCLASSW ui_app_wc; // window class\n\nstatic NONCLIENTMETRICSW ui_app_ncm = { sizeof(NONCLIENTMETRICSW) };\nstatic MONITORINFO ui_app_mi = {sizeof(MONITORINFO)};\n\nstatic rt_event_t ui_app_event_quit;\nstatic rt_event_t ui_app_event_invalidate;\nstatic rt_event_t ui_app_wt; // waitable timer;\n\nstatic rt_work_queue_t ui_app_queue;\n\nstatic uintptr_t ui_app_timer_1s_id;\nstatic uintptr_t ui_app_timer_100ms_id;\n\nstatic bool ui_app_layout_dirty; // call layout() before paint\n\nstatic char ui_app_decoded_pressed[16];  // utf8 of last decoded pressed key\nstatic char ui_app_decoded_released[16]; // utf8 of last decoded released key\nstatic uint16_t ui_app_high_surrogate;\n\ntypedef void (*ui_app_animate_function_t)(int32_t step);\n\nstatic struct {\n    ui_app_animate_function_t f;\n    int32_t count;\n    int32_t step;\n    ui_timer_t timer;\n} ui_app_animate;\n\n// Animation timer is Windows minimum of 10ms, but in reality the timer\n// messages are far from isochronous and more likely to arrive at 16 or\n// 32ms intervals and can be delayed.\n\nstatic void ui_app_post_message(int32_t m, int64_t wp, int64_t lp) {\n    rt_fatal_win32err(PostMessageA(ui_app_window(), (UINT)m,\n            (WPARAM)wp, (LPARAM)lp));\n}\n\nstatic void ui_app_update_wt_timeout(void) {\n    fp64_t next_due_at = -1.0;\n    rt_atomics.spinlock_acquire(&ui_app_queue.lock);\n    if (ui_app_queue.head != null) {\n        next_due_at = ui_app_queue.head->when;\n    }\n    rt_atomics.spinlock_release(&ui_app_queue.lock);\n    if (next_due_at >= 0) {\n        static fp64_t last_next_due_at;\n        fp64_t dt = next_due_at - rt_clock.seconds();\n        if (dt <= 0) {\n            ui_app_post_message(WM_NULL, 0, 0);\n        } else if (last_next_due_at != next_due_at) {\n            // Negative values indicate relative time in 100ns intervals\n            LARGE_INTEGER rt = {0}; // relative negative time\n            rt.QuadPart = (LONGLONG)(-dt * 1.0E+7);\n            rt_swear(rt.QuadPart < 0, \"dt: %.6f %lld\", dt, rt.QuadPart);\n            rt_fatal_win32err(\n                SetWaitableTimer(ui_app_wt, &rt, 0, null, null, 0)\n            );\n        }\n        last_next_due_at = next_due_at;\n    }\n}\n\nstatic void ui_app_post(rt_work_t* w) {\n    if (w->queue == null) { w->queue = &ui_app_queue; }\n    // work item can be reused but only with the same queue\n    rt_assert(w->queue == &ui_app_queue);\n    rt_work_queue.post(w);\n    ui_app_update_wt_timeout();\n}\n\nstatic void ui_app_alarm_thread(void* rt_unused(p)) {\n    rt_thread.realtime();\n    rt_thread.name(\"ui_app.alarm\");\n    for (;;) {\n        rt_event_t es[] = { ui_app_wt, ui_app_event_quit };\n        int32_t ix = rt_event.wait_any(rt_countof(es), es);\n        if (ix == 0) {\n            ui_app_post_message(WM_NULL, 0, 0);\n        } else {\n            break;\n        }\n    }\n}\n\n\n// InvalidateRect() may wait for up to 30 milliseconds\n// which is unacceptable for video drawing at monitor\n// refresh rate\n\nstatic void ui_app_redraw_thread(void* rt_unused(p)) {\n    rt_thread.realtime();\n    rt_thread.name(\"ui_app.redraw\");\n    for (;;) {\n        rt_event_t es[] = { ui_app_event_invalidate, ui_app_event_quit };\n        int32_t ix = rt_event.wait_any(rt_countof(es), es);\n        if (ix == 0) {\n            if (ui_app_window() != null) {\n                InvalidateRect(ui_app_window(), null, false);\n            }\n        } else {\n            break;\n        }\n    }\n}\n\n\n// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes\n// https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown\n\nstatic void ui_app_alt_ctrl_shift(bool down, int64_t key) {\n    if (key == VK_MENU)    { ui_app.alt   = down; }\n    if (key == VK_CONTROL) { ui_app.ctrl  = down; }\n    if (key == VK_SHIFT)   { ui_app.shift = down; }\n}\n\nstatic inline ui_point_t ui_app_point2ui(const POINT* p) {\n    ui_point_t u = { p->x, p->y };\n    return u;\n}\n\nstatic inline POINT ui_app_ui2point(const ui_point_t* u) {\n    POINT p = { u->x, u->y };\n    return p;\n}\n\nstatic ui_rect_t ui_app_rect2ui(const RECT* r) {\n    ui_rect_t u = { r->left, r->top, r->right - r->left, r->bottom - r->top };\n    return u;\n}\n\nstatic RECT ui_app_ui2rect(const ui_rect_t* u) {\n    RECT r = { u->x, u->y, u->x + u->w, u->y + u->h };\n    return r;\n}\n\nstatic void ui_app_update_ncm(int32_t dpi) {\n    // Only UTF-16 version supported SystemParametersInfoForDpi\n    rt_fatal_win32err(SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS,\n        sizeof(ui_app_ncm), &ui_app_ncm, 0, (DWORD)dpi));\n}\n\nstatic void ui_app_update_monitor_dpi(HMONITOR monitor, ui_dpi_t* dpi) {\n    dpi->monitor_max = 72;\n    for (int32_t mtd = MDT_EFFECTIVE_DPI; mtd <= MDT_RAW_DPI; mtd++) {\n        uint32_t dpi_x = 0;\n        uint32_t dpi_y = 0;\n        // GetDpiForMonitor() may return ERROR_GEN_FAILURE 0x8007001F when\n        // system wakes up from sleep:\n        // \"\"A device attached to the system is not functioning.\"\n        // docs say:\n        // \"May be used to indicate that the device has stopped responding\n        // or a general failure has occurred on the device.\n        // The device may need to be manually reset.\"\n        int32_t r = GetDpiForMonitor(monitor, (MONITOR_DPI_TYPE)mtd, &dpi_x, &dpi_y);\n        if (r != 0) {\n            rt_thread.sleep_for(1.0 / 32); // and retry:\n            r = GetDpiForMonitor(monitor, (MONITOR_DPI_TYPE)mtd, &dpi_x, &dpi_y);\n        }\n        if (r == 0) {\n            // EFFECTIVE_DPI 168 168 (with regard of user scaling)\n            // ANGULAR_DPI 247 248 (diagonal)\n            // RAW_DPI 283 284 (horizontal, vertical)\n            // Parallels Desktop 16.5.0 (49183) on macOS Mac Book Air\n            // EFFECTIVE_DPI 192 192 (with regard of user scaling)\n            // ANGULAR_DPI 224 224 (diagonal)\n            // RAW_DPI 72 72\n            const int32_t max_xy = (int32_t)rt_max(dpi_x, dpi_y);\n            switch (mtd) {\n                case MDT_EFFECTIVE_DPI:\n                    dpi->monitor_effective = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_effective := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                case MDT_ANGULAR_DPI:\n                    dpi->monitor_angular = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_angular := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                case MDT_RAW_DPI:\n                    dpi->monitor_raw = max_xy;\n//                  rt_println(\"ui_app.dpi.monitor_raw := max(%d,%d)\", dpi_x, dpi_y);\n                    break;\n                default: rt_assert(false);\n            }\n            dpi->monitor_max = rt_max(dpi->monitor_max, max_xy);\n        }\n    }\n//  rt_println(\"ui_app.dpi.monitor_max := %d\", dpi->monitor_max);\n}\n\n#ifndef UI_APP_DEBUG\n\nstatic void ui_app_dump_dpi(void) {\n    rt_println(\"ui_app.dpi.monitor_effective: %d\", ui_app.dpi.monitor_effective  );\n    rt_println(\"ui_app.dpi.monitor_angular  : %d\", ui_app.dpi.monitor_angular    );\n    rt_println(\"ui_app.dpi.monitor_raw      : %d\", ui_app.dpi.monitor_raw        );\n    rt_println(\"ui_app.dpi.monitor_max      : %d\", ui_app.dpi.monitor_max        );\n    rt_println(\"ui_app.dpi.window           : %d\", ui_app.dpi.window             );\n    rt_println(\"ui_app.dpi.system           : %d\", ui_app.dpi.system             );\n    rt_println(\"ui_app.dpi.process          : %d\", ui_app.dpi.process            );\n    rt_println(\"ui_app.mrc      : %d,%d %dx%d\", ui_app.mrc.x, ui_app.mrc.y,\n                                             ui_app.mrc.w, ui_app.mrc.h);\n    rt_println(\"ui_app.wrc      : %d,%d %dx%d\", ui_app.wrc.x, ui_app.wrc.y,\n                                             ui_app.wrc.w, ui_app.wrc.h);\n    rt_println(\"ui_app.crc      : %d,%d %dx%d\", ui_app.crc.x, ui_app.crc.y,\n                                             ui_app.crc.w, ui_app.crc.h);\n    rt_println(\"ui_app.work_area: %d,%d %dx%d\", ui_app.work_area.x, ui_app.work_area.y,\n                                             ui_app.work_area.w, ui_app.work_area.h);\n    int32_t mxt_x = GetSystemMetrics(SM_CXMAXTRACK);\n    int32_t mxt_y = GetSystemMetrics(SM_CYMAXTRACK);\n    rt_println(\"MAXTRACK: %d, %d\", mxt_x, mxt_y);\n    int32_t scr_x = GetSystemMetrics(SM_CXSCREEN);\n    int32_t scr_y = GetSystemMetrics(SM_CYSCREEN);\n    fp64_t monitor_x = (fp64_t)scr_x / (fp64_t)ui_app.dpi.monitor_max;\n    fp64_t monitor_y = (fp64_t)scr_y / (fp64_t)ui_app.dpi.monitor_max;\n    rt_println(\"SCREEN: %d, %d %.1fx%.1f\\\"\", scr_x, scr_y, monitor_x, monitor_y);\n}\n\n#endif\n\nstatic bool ui_app_update_mi(const ui_rect_t* r, uint32_t flags) {\n    RECT rc = ui_app_ui2rect(r);\n    HMONITOR monitor = MonitorFromRect(&rc, flags);\n//  TODO: moving between monitors with different DPIs\n//  HMONITOR mw = MonitorFromWindow(ui_app_window(), flags);\n    if (monitor != null) {\n        ui_app_update_monitor_dpi(monitor, &ui_app.dpi);\n        rt_fatal_win32err(GetMonitorInfoA(monitor, &ui_app_mi));\n        ui_app.work_area = ui_app_rect2ui(&ui_app_mi.rcWork);\n        ui_app.mrc = ui_app_rect2ui(&ui_app_mi.rcMonitor);\n//      ui_app_dump_dpi();\n    }\n    return monitor != null;\n}\n\nstatic void ui_app_update_crc(void) {\n    RECT rc = {0};\n    rt_fatal_win32err(GetClientRect(ui_app_window(), &rc));\n    ui_app.crc = ui_app_rect2ui(&rc);\n}\n\nstatic void ui_app_dispose_fonts(void) {\n    ui_gdi.delete_font(ui_app.fm.prop.normal.font);\n    ui_gdi.delete_font(ui_app.fm.prop.tiny.font);\n    ui_gdi.delete_font(ui_app.fm.prop.title.font);\n    ui_gdi.delete_font(ui_app.fm.prop.rubric.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H1.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H2.font);\n    ui_gdi.delete_font(ui_app.fm.prop.H3.font);\n    memset(&ui_app.fm.prop, 0x00, sizeof(ui_app.fm.prop));\n    ui_gdi.delete_font(ui_app.fm.mono.normal.font);\n    ui_gdi.delete_font(ui_app.fm.mono.tiny.font);\n    ui_gdi.delete_font(ui_app.fm.mono.title.font);\n    ui_gdi.delete_font(ui_app.fm.mono.rubric.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H1.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H2.font);\n    ui_gdi.delete_font(ui_app.fm.mono.H3.font);\n    memset(&ui_app.fm.mono, 0x00, sizeof(ui_app.fm.mono));\n}\n\nstatic fp64_t ui_app_px2pt(fp64_t px) {\n    rt_assert(ui_app.dpi.window >= 72.0);\n    return px * 72.0 / (fp64_t)ui_app.dpi.window;\n}\n\nstatic int32_t ui_app_pt2px(fp64_t pt) { // rounded\n    return (int32_t)(pt * (fp64_t)ui_app.dpi.window / 72.0 + 0.5);\n}\n\nstatic void ui_app_init_cursors(void) {\n    if (ui_app.cursors.arrow == null) {\n        ui_app.cursors.arrow     = (ui_cursor_t)LoadCursorW(null, IDC_ARROW);\n        ui_app.cursors.wait      = (ui_cursor_t)LoadCursorW(null, IDC_WAIT);\n        ui_app.cursors.ibeam     = (ui_cursor_t)LoadCursorW(null, IDC_IBEAM);\n        ui_app.cursors.size_nwse = (ui_cursor_t)LoadCursorW(null, IDC_SIZENWSE);\n        ui_app.cursors.size_nesw = (ui_cursor_t)LoadCursorW(null, IDC_SIZENESW);\n        ui_app.cursors.size_we   = (ui_cursor_t)LoadCursorW(null, IDC_SIZEWE);\n        ui_app.cursors.size_ns   = (ui_cursor_t)LoadCursorW(null, IDC_SIZENS);\n        ui_app.cursors.size_all  = (ui_cursor_t)LoadCursorW(null, IDC_SIZEALL);\n        ui_app.cursor = ui_app.cursors.arrow;\n    }\n}\n\nstatic void ui_app_ncm_dump_fonts(void) {\n    // Win10/Win11 all 5 fonts are exactly the same:\n//  Caption  : Segoe UI 0x-12 weight: 400 quality: 0\n//  SmCaption: Segoe UI 0x-12 weight: 400 quality: 0\n//  Menu     : Segoe UI 0x-12 weight: 400 quality: 0\n//  Status   : Segoe UI 0x-12 weight: 400 quality: 0\n//  Message  : Segoe UI 0x-12 weight: 400 quality: 0\n#if 0\n    const LOGFONTW* fonts[] = {\n        &ui_app_ncm.lfCaptionFont, &ui_app_ncm.lfSmCaptionFont,\n        &ui_app_ncm.lfMenuFont, &ui_app_ncm.lfStatusFont,\n        &ui_app_ncm.lfMessageFont\n    };\n    const char* font_names[] = {\n        \"Caption\", \"SmCaption\", \"Menu\", \"Status\", \"Message\"\n    };\n    for (int32_t i = 0; i < rt_countof(fonts); i++) {\n        const LOGFONTW* lf = fonts[i];\n        char fn[128];\n        rt_str.utf16to8(fn, rt_countof(fn), lf->lfFaceName, -1);\n        rt_println(\"%-9s: %s %dx%d weight: %d quality: %d\", font_names[i], fn,\n                   lf->lfWidth, lf->lfHeight, lf->lfWeight, lf->lfQuality);\n    }\n#endif\n}\n\nstatic void ui_app_dump_font_size(const char* name, const LOGFONTW* lf,\n                                  ui_fm_t* fm) {\n    rt_swear(abs(lf->lfHeight) == fm->height - fm->internal_leading);\n    rt_swear(fm->external_leading == 0); // \"Segoe UI\" and \"Cascadia Mono\"\n    rt_swear(ui_app.dpi.window >= 72);\n    // \"The height, in logical units, of the font's character cell or character.\n    //  The character height value (also known as the em height) is the\n    //  character cell height value minus the internal-leading value.\"\n    #ifdef UI_APP_DUMP_FONT_SIZE\n        int32_t ascender = fm->baseline - fm->ascent;\n        int32_t cell = fm->height - ascender - fm->descent;\n        fp64_t  pt = fm->height * 72.0 / (fp64_t)ui_app.dpi.window;\n        rt_println(\"%-6s .lfH: %+3d h: %d pt: %6.3f \"\n                   \"a: %2d c: %2d d: %d bl: %2d il: %2d lg: %d\",\n                    name, lf->lfHeight, fm->height, pt,\n                    ascender, cell, fm->descent, fm->baseline,\n                    fm->internal_leading, fm->line_gap);\n        #if 0 // TODO: need better understanding of box geometry in\n              // \"design units\"\n            // box scale factor: design units -> pixels\n            fp64_t  sf = pt * 72.0 / (fp64_t)fm->design_units_per_em;\n            sf *= (fp64_t)ui_app.dpi.window / 72.0; // into pixels (unclear???)\n            int32_t bx = (int32_t)(fm->box.x * sf + 0.5);\n            int32_t by = (int32_t)(fm->box.y * sf + 0.5);\n            int32_t bw = (int32_t)(fm->box.w * sf + 0.5);\n            int32_t bh = (int32_t)(fm->box.h * sf + 0.5);\n            rt_println(\"%-6s .box: %d,%d %dx%d\", name, bx, by, bw, bh);\n        #endif\n    #else\n        (void)name; // unused\n    #endif\n}\n\nstatic void ui_app_init_fms(ui_fms_t* fms, const LOGFONTW* base) {\n    LOGFONTW lf = *base;\n    // lf.lfQuality is zero (DEFAULT_QUALITY) that gets internally\n    // interpreted as CLEARTYPE_QUALITY (if clear type is enabled\n    // system wide and it looks really bad on 4K monitors\n    // Experimentally it looks like Windows UI is using PROOF_QUALITY\n    // which is anti-aliased w/o ClearType rainbows\n    // TODO: maybe DEFAULT_QUALITY on 96DPI,\n    //             PROOF_QUALITY below 4K\n    //             ANTIALIASED_QUALITY on 4K and ?\n    lf.lfQuality = ANTIALIASED_QUALITY;\n    ui_gdi.update_fm(&fms->normal, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"normal\", &lf, &fms->normal);\n    const fp64_t fh = lf.lfHeight;\n    rt_swear(fh != 0);\n    lf.lfHeight = (int32_t)(fh * 8.0 / 11.0 + 0.5);\n    ui_gdi.update_fm(&fms->tiny, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"tiny\", &lf, &fms->tiny);\n\n    lf.lfWeight = FW_SEMIBOLD;\n    lf.lfHeight = (int32_t)(fh * 2.25 + 0.5);\n    ui_gdi.update_fm(&fms->title, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"title\", &lf, &fms->title);\n    lf.lfHeight = (int32_t)(fh * 2.00 + 0.5);\n    ui_gdi.update_fm(&fms->rubric, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"rubric\", &lf, &fms->rubric);\n    lf.lfHeight = (int32_t)(fh * 1.75 + 0.5);\n    ui_gdi.update_fm(&fms->H1, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H1\", &lf, &fms->H1);\n    lf.lfHeight = (int32_t)(fh * 1.4 + 0.5);\n    ui_gdi.update_fm(&fms->H2, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H2\", &lf, &fms->H2);\n    lf.lfHeight = (int32_t)(fh * 1.15 + 0.5);\n    ui_gdi.update_fm(&fms->H3, (ui_font_t)CreateFontIndirectW(&lf));\n    ui_app_dump_font_size(\"H3\", &lf, &fms->H3);\n}\n\nstatic void ui_app_init_fonts(int32_t dpi) {\n    ui_app_update_ncm(dpi);\n    ui_app_ncm_dump_fonts();\n    if (ui_app.fm.prop.normal.font != null) { ui_app_dispose_fonts(); }\n    LOGFONTW mono = ui_app_ncm.lfMessageFont;\n    // TODO: how to get name of monospaced from Win32 API?\n    wcscpy_s(mono.lfFaceName, rt_countof(mono.lfFaceName), L\"Cascadia Mono\");\n    mono.lfPitchAndFamily |= FIXED_PITCH;\n//  rt_println(\"ui_app.fm.mono\");\n    ui_app_init_fms(&ui_app.fm.mono, &mono);\n    LOGFONTW prop = ui_app_ncm.lfMessageFont;\n    prop.lfHeight--; // inc by 1\n//  rt_println(\"ui_app.fm.prop\");\n    ui_app_init_fms(&ui_app.fm.prop, &ui_app_ncm.lfMessageFont);\n}\n\nstatic void ui_app_data_save(const char* name, const void* data, int32_t bytes) {\n    rt_config.save(ui_app.class_name, name, data, bytes);\n}\n\nstatic int32_t ui_app_data_size(const char* name) {\n    return rt_config.size(ui_app.class_name, name);\n}\n\nstatic int32_t ui_app_data_load(const char* name, void* data, int32_t bytes) {\n    return rt_config.load(ui_app.class_name, name, data, bytes);\n}\n\ntypedef rt_begin_packed struct ui_app_wiw_s { // \"where is window\"\n    // coordinates in pixels relative (0,0) top left corner\n    // of primary monitor from GetWindowPlacement\n    int32_t    bytes;\n    int32_t    padding;      // to align rectangles and points to 8 bytes\n    ui_rect_t  placement;\n    ui_rect_t  mrc;          // monitor rectangle\n    ui_rect_t  work_area;    // monitor work area (mrc sans taskbar etc)\n    ui_point_t min_position; // not used (-1, -1)\n    ui_point_t max_position; // not used (-1, -1)\n    ui_point_t max_track;    // maximum window size (spawning all monitors)\n    ui_rect_t  space;        // surrounding rect x,y,w,h of all monitors\n    int32_t    dpi;          // of the monitor on which window (x,y) is located\n    int32_t    flags;        // WPF_SETMINPOSITION. WPF_RESTORETOMAXIMIZED\n    int32_t    show;         // show command\n} rt_end_packed ui_app_wiw_t;\n\nstatic BOOL CALLBACK ui_app_monitor_enum_proc(HMONITOR monitor,\n        HDC rt_unused(hdc), RECT* rt_unused(rc1), LPARAM that) {\n    ui_app_wiw_t* wiw = (ui_app_wiw_t*)(uintptr_t)that;\n    MONITORINFOEXA mi = { .cbSize = sizeof(MONITORINFOEXA) };\n    rt_fatal_win32err(GetMonitorInfoA(monitor, (MONITORINFO*)&mi));\n    // monitors can be in negative coordinate spaces and even rotated upside-down\n    const int32_t min_x = rt_min(mi.rcMonitor.left, mi.rcMonitor.right);\n    const int32_t min_y = rt_min(mi.rcMonitor.top,  mi.rcMonitor.bottom);\n    const int32_t max_w = rt_max(mi.rcMonitor.left, mi.rcMonitor.right);\n    const int32_t max_h = rt_max(mi.rcMonitor.top,  mi.rcMonitor.bottom);\n    wiw->space.x = rt_min(wiw->space.x, min_x);\n    wiw->space.y = rt_min(wiw->space.y, min_y);\n    wiw->space.w = rt_max(wiw->space.w, max_w);\n    wiw->space.h = rt_max(wiw->space.h, max_h);\n    return true; // keep going\n}\n\nstatic void ui_app_enum_monitors(ui_app_wiw_t* wiw) {\n    EnumDisplayMonitors(null, null, ui_app_monitor_enum_proc,\n        (LPARAM)(uintptr_t)wiw);\n    // because ui_app_monitor_enum_proc() puts max into w,h:\n    wiw->space.w -= wiw->space.x;\n    wiw->space.h -= wiw->space.y;\n}\n\nstatic void ui_app_save_window_pos(ui_window_t wnd, const char* name, bool dump) {\n    RECT wr = {0};\n    rt_fatal_win32err(GetWindowRect((HWND)wnd, &wr));\n    ui_rect_t wrc = ui_app_rect2ui(&wr);\n    ui_app_update_mi(&wrc, MONITOR_DEFAULTTONEAREST);\n    WINDOWPLACEMENT wpl = { .length = sizeof(wpl) };\n    rt_fatal_win32err(GetWindowPlacement((HWND)wnd, &wpl));\n    // note the replacement of wpl.rcNormalPosition with wrc:\n    ui_app_wiw_t wiw = { // where is window\n        .bytes = sizeof(ui_app_wiw_t),\n        .placement = wrc,\n        .mrc = ui_app.mrc,\n        .work_area = ui_app.work_area,\n        .min_position = ui_app_point2ui(&wpl.ptMinPosition),\n        .max_position = ui_app_point2ui(&wpl.ptMaxPosition),\n        .max_track = {\n            .x = GetSystemMetrics(SM_CXMAXTRACK),\n            .y = GetSystemMetrics(SM_CYMAXTRACK)\n        },\n        .dpi = ui_app.dpi.monitor_max,\n        .flags = (int32_t)wpl.flags,\n        .show  = (int32_t)wpl.showCmd\n    };\n    ui_app_enum_monitors(&wiw);\n    if (dump) {\n        rt_println(\"wiw.space: %d,%d %dx%d\",\n              wiw.space.x, wiw.space.y, wiw.space.w, wiw.space.h);\n        rt_println(\"MAXTRACK: %d, %d\", wiw.max_track.x, wiw.max_track.y);\n        rt_println(\"wpl.rcNormalPosition: %d,%d %dx%d\",\n            wpl.rcNormalPosition.left, wpl.rcNormalPosition.top,\n            wpl.rcNormalPosition.right - wpl.rcNormalPosition.left,\n            wpl.rcNormalPosition.bottom - wpl.rcNormalPosition.top);\n        rt_println(\"wpl.ptMinPosition: %d,%d\",\n            wpl.ptMinPosition.x, wpl.ptMinPosition.y);\n        rt_println(\"wpl.ptMaxPosition: %d,%d\",\n            wpl.ptMaxPosition.x, wpl.ptMaxPosition.y);\n        rt_println(\"wpl.showCmd: %d\", wpl.showCmd);\n        // WPF_SETMINPOSITION. WPF_RESTORETOMAXIMIZED WPF_ASYNCWINDOWPLACEMENT\n        rt_println(\"wpl.flags: %d\", wpl.flags);\n    }\n//  rt_println(\"%d,%d %dx%d show=%d\", wiw.placement.x, wiw.placement.y,\n//      wiw.placement.w, wiw.placement.h, wiw.show);\n    rt_config.save(ui_app.class_name, name, &wiw, sizeof(wiw));\n    ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n}\n\nstatic void ui_app_save_console_pos(void) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null) {\n        ui_app_save_window_pos((ui_window_t)cw, \"wic\", false);\n        HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n        CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n        int32_t r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n        if (r != 0) {\n            rt_println(\"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n        } else {\n            rt_config.save(ui_app.class_name, \"console_screen_buffer_infoex\",\n                            &info, (int32_t)sizeof(info));\n//          rt_println(\"info: %dx%d\", info.dwSize.X, info.dwSize.Y);\n//          rt_println(\"%d,%d %dx%d\", info.srWindow.Left, info.srWindow.Top,\n//              info.srWindow.Right - info.srWindow.Left,\n//              info.srWindow.Bottom - info.srWindow.Top);\n        }\n    }\n    int32_t v = ui_app.is_console_visible();\n    // \"icv\" \"is console visible\"\n    rt_config.save(ui_app.class_name, \"icv\", &v, (int32_t)sizeof(v));\n}\n\nstatic bool ui_app_is_fully_inside(const ui_rect_t* inner,\n                                const ui_rect_t* outer) {\n    return\n        outer->x <= inner->x && inner->x + inner->w <= outer->x + outer->w &&\n        outer->y <= inner->y && inner->y + inner->h <= outer->y + outer->h;\n}\n\nstatic void ui_app_bring_window_inside_monitor(const ui_rect_t* mrc, ui_rect_t* wrc) {\n    rt_assert(mrc->w > 0 && mrc->h > 0);\n    // Check if window rect is inside monitor rect\n    if (!ui_app_is_fully_inside(wrc, mrc)) {\n        // Move window into monitor rect\n        wrc->x = rt_max(mrc->x, rt_min(mrc->x + mrc->w - wrc->w, wrc->x));\n        wrc->y = rt_max(mrc->y, rt_min(mrc->y + mrc->h - wrc->h, wrc->y));\n        // Adjust size to fit into monitor rect\n        wrc->w = rt_min(wrc->w, mrc->w);\n        wrc->h = rt_min(wrc->h, mrc->h);\n    }\n}\n\nstatic bool ui_app_load_window_pos(ui_rect_t* rect, int32_t *visibility) {\n    ui_app_wiw_t wiw = {0}; // where is window\n    bool loaded = rt_config.load(ui_app.class_name, \"wiw\", &wiw, sizeof(wiw)) ==\n                                sizeof(wiw);\n    if (loaded) {\n        #ifdef UI_APP_DEBUG\n            rt_println(\"wiw.placement: %d,%d %dx%d\", wiw.placement.x, wiw.placement.y,\n                wiw.placement.w, wiw.placement.h);\n            rt_println(\"wiw.mrc: %d,%d %dx%d\", wiw.mrc.x, wiw.mrc.y, wiw.mrc.w, wiw.mrc.h);\n            rt_println(\"wiw.work_area: %d,%d %dx%d\", wiw.work_area.x, wiw.work_area.y,\n                                                  wiw.work_area.w, wiw.work_area.h);\n            rt_println(\"wiw.min_position: %d,%d\", wiw.min_position.x, wiw.min_position.y);\n            rt_println(\"wiw.max_position: %d,%d\", wiw.max_position.x, wiw.max_position.y);\n            rt_println(\"wiw.max_track: %d,%d\", wiw.max_track.x, wiw.max_track.y);\n            rt_println(\"wiw.dpi: %d\", wiw.dpi);\n            rt_println(\"wiw.flags: %d\", wiw.flags);\n            rt_println(\"wiw.show: %d\", wiw.show);\n        #endif\n        ui_app_update_mi(&wiw.placement, MONITOR_DEFAULTTONEAREST);\n        bool same_monitor = memcmp(&wiw.mrc, &ui_app.mrc, sizeof(wiw.mrc)) == 0;\n//      rt_println(\"%d,%d %dx%d\", p->x, p->y, p->w, p->h);\n        if (same_monitor) {\n            *rect = wiw.placement;\n        } else { // moving to another monitor\n            rect->x = (wiw.placement.x - wiw.mrc.x) * ui_app.mrc.w / wiw.mrc.w;\n            rect->y = (wiw.placement.y - wiw.mrc.y) * ui_app.mrc.h / wiw.mrc.h;\n            // adjust according to monitors DPI difference:\n            // (w, h) theoretically could be as large as 0xFFFF\n            const int64_t w = (int64_t)wiw.placement.w * ui_app.dpi.monitor_max;\n            const int64_t h = (int64_t)wiw.placement.h * ui_app.dpi.monitor_max;\n            rect->w = (int32_t)(w / wiw.dpi);\n            rect->h = (int32_t)(h / wiw.dpi);\n        }\n        *visibility = wiw.show;\n    }\n//  rt_println(\"%d,%d %dx%d show=%d\", rect->x, rect->y, rect->w, rect->h, *visibility);\n    ui_app_bring_window_inside_monitor(&ui_app.mrc, rect);\n//  rt_println(\"%d,%d %dx%d show=%d\", rect->x, rect->y, rect->w, rect->h, *visibility);\n    return loaded;\n}\n\nstatic bool ui_app_load_console_pos(ui_rect_t* rect, int32_t *visibility) {\n    ui_app_wiw_t wiw = {0}; // where is window\n    *visibility = 0; // boolean\n    bool loaded = rt_config.load(ui_app.class_name, \"wic\", &wiw, sizeof(wiw)) ==\n                                sizeof(wiw);\n    if (loaded) {\n        ui_app_update_mi(&wiw.placement, MONITOR_DEFAULTTONEAREST);\n        bool same_monitor = memcmp(&wiw.mrc, &ui_app.mrc, sizeof(wiw.mrc)) == 0;\n//      rt_println(\"%d,%d %dx%d\", p->x, p->y, p->w, p->h);\n        if (same_monitor) {\n            *rect = wiw.placement;\n        } else { // moving to another monitor\n            rect->x = (wiw.placement.x - wiw.mrc.x) * ui_app.mrc.w / wiw.mrc.w;\n            rect->y = (wiw.placement.y - wiw.mrc.y) * ui_app.mrc.h / wiw.mrc.h;\n            // adjust according to monitors DPI difference:\n            // (w, h) theoretically could be as large as 0xFFFF\n            const int64_t w = (int64_t)wiw.placement.w * ui_app.dpi.monitor_max;\n            const int64_t h = (int64_t)wiw.placement.h * ui_app.dpi.monitor_max;\n            rect->w = (int32_t)(w / wiw.dpi);\n            rect->h = (int32_t)(h / wiw.dpi);\n        }\n        *visibility = wiw.show != 0;\n        ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n    }\n    return loaded;\n}\n\nstatic void ui_app_timer_kill(ui_timer_t timer) {\n    rt_fatal_win32err(KillTimer(ui_app_window(), timer));\n}\n\nstatic ui_timer_t ui_app_timer_set(uintptr_t id, int32_t ms) {\n    rt_not_null(ui_app_window());\n    rt_assert(10 <= ms && ms < 0x7FFFFFFF);\n    ui_timer_t tid = (ui_timer_t)SetTimer(ui_app_window(), id, (uint32_t)ms, null);\n    rt_fatal_if(tid == 0);\n    rt_assert(tid == id);\n    return tid;\n}\n\nstatic void ui_app_timer(ui_view_t* view, ui_timer_t id) {\n    ui_view.timer(view, id);\n    if (id == ui_app_timer_1s_id) { ui_view.every_sec(view); }\n    if (id == ui_app_timer_100ms_id) { ui_view.every_100ms(view); }\n}\n\nstatic void ui_app_animate_timer(void) {\n    ui_app_post_message(ui.message.animate, (int64_t)ui_app_animate.step + 1,\n        (int64_t)(uintptr_t)ui_app_animate.f);\n}\n\nstatic void ui_app_wm_timer(ui_timer_t id) {\n    if (ui_app.animating.time != 0 && ui_app.now > ui_app.animating.time) {\n        ui_app.show_toast(null, 0);\n    }\n    if (ui_app_animate.timer == id) { ui_app_animate_timer(); }\n    ui_app_timer(ui_app.root, id);\n}\n\nstatic void ui_app_window_dpi(void) {\n    int32_t dpi = (int32_t)GetDpiForWindow(ui_app_window());\n    if (dpi == 0) { dpi = (int32_t)GetDpiForWindow(GetParent(ui_app_window())); }\n    if (dpi == 0) { dpi = (int32_t)GetDpiForWindow(GetDesktopWindow()); }\n    if (dpi == 0) { dpi = (int32_t)GetSystemDpiForProcess(GetCurrentProcess()); }\n    if (dpi == 0) { dpi = (int32_t)GetDpiForSystem(); }\n    ui_app.dpi.window = dpi;\n}\n\nstatic void ui_app_window_opening(void) {\n    ui_app_window_dpi();\n    ui_app_init_fonts(ui_app.dpi.window);\n    ui_app_init_cursors();\n    ui_app_timer_1s_id = ui_app.set_timer((uintptr_t)&ui_app_timer_1s_id, 1000);\n    ui_app_timer_100ms_id = ui_app.set_timer((uintptr_t)&ui_app_timer_100ms_id, 100);\n    rt_assert(ui_app.cursors.arrow != null);\n    ui_app.set_cursor(ui_app.cursors.arrow);\n    ui_app.canvas = (ui_canvas_t)GetDC(ui_app_window());\n    rt_not_null(ui_app.canvas);\n    if (ui_app.opened != null) { ui_app.opened(); }\n    ui_view.set_text(ui_app.root, \"ui_app.root\"); // debugging\n    ui_app_wm_timer(ui_app_timer_100ms_id);\n    ui_app_wm_timer(ui_app_timer_1s_id);\n    rt_fatal_if(ReleaseDC(ui_app_window(), ui_app_canvas()) == 0);\n    ui_app.canvas = null;\n    ui_app.request_layout(); // request layout\n    if (ui_app.last_visibility == ui.visibility.maximize) {\n        ShowWindow(ui_app_window(), ui.visibility.maximize);\n    }\n//  ui_app_dump_dpi();\n//  if (forced_locale != 0) {\n//      SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (uintptr_t)\"intl\", 0, 1000, null);\n//  }\n}\n\nstatic void ui_app_window_closing(void) {\n    if (ui_app.can_close == null || ui_app.can_close()) {\n        if (ui_app.is_full_screen) { ui_app.full_screen(false); }\n        ui_app.kill_timer(ui_app_timer_1s_id);\n        ui_app.kill_timer(ui_app_timer_100ms_id);\n        ui_app_timer_1s_id = 0;\n        ui_app_timer_100ms_id = 0;\n        if (ui_app.closed != null) { ui_app.closed(); }\n        ui_app_save_window_pos(ui_app.window, \"wiw\", false);\n        ui_app_save_console_pos();\n        DestroyWindow(ui_app_window());\n        ui_app.window = null;\n    }\n}\n\nstatic void ui_app_get_min_max_info(MINMAXINFO* mmi) {\n    const ui_window_sizing_t* ws = &ui_app.window_sizing;\n    const ui_rect_t* wa = &ui_app.work_area;\n    const int32_t min_w = ws->min_w > 0 ? ui_app.in2px(ws->min_w) : ui_app.in2px(1.0);\n    const int32_t min_h = ws->min_h > 0 ? ui_app.in2px(ws->min_h) : ui_app.in2px(0.5);\n    mmi->ptMinTrackSize.x = min_w;\n    mmi->ptMinTrackSize.y = min_h;\n    const int32_t max_w = ws->max_w > 0 ? ui_app.in2px(ws->max_w) : wa->w;\n    const int32_t max_h = ws->max_h > 0 ? ui_app.in2px(ws->max_h) : wa->h;\n    if (ui_app.no_clip) {\n        mmi->ptMaxTrackSize.x = max_w;\n        mmi->ptMaxTrackSize.y = max_h;\n    } else {\n        // clip max_w and max_h to monitor work area\n        mmi->ptMaxTrackSize.x = rt_min(max_w, wa->w);\n        mmi->ptMaxTrackSize.y = rt_min(max_h, wa->h);\n    }\n    mmi->ptMaxSize.x = mmi->ptMaxTrackSize.x;\n    mmi->ptMaxSize.y = mmi->ptMaxTrackSize.y;\n}\n\nstatic void ui_app_paint(ui_view_t* view) {\n    rt_assert(ui_app_window() != null);\n    // crc = {0,0} on minimized windows but paint is still called\n    if (ui_app.crc.w > 0 && ui_app.crc.h > 0) { ui_view.paint(view); }\n}\n\nstatic void ui_app_measure_and_layout(ui_view_t* view) {\n    // restore from minimized calls ui_app.crc.w,h == 0\n    if (ui_app.crc.w > 0 && ui_app.crc.h > 0 && ui_app_window() != null) {\n        ui_view.measure(view);\n        ui_view.layout(view);\n        ui_app_layout_dirty = false;\n    }\n}\n\nstatic void ui_app_toast_character(const char* utf8);\nstatic bool ui_app_toast_key_pressed(int64_t key);\nstatic bool ui_app_toast_tap(ui_view_t* v, int32_t ix, bool pressed);\n\nstatic void ui_app_dispatch_wm_char(ui_view_t* view, const uint16_t* utf16) {\n    char utf8[32 + 1];\n    int32_t utf8bytes = rt_str.utf8_bytes(utf16, -1);\n    rt_swear(utf8bytes < rt_countof(utf8) - 1); // 32 bytes + 0x00\n    rt_str.utf16to8(utf8, rt_countof(utf8), utf16, -1);\n    utf8[utf8bytes] = 0x00;\n    if (ui_app.animating.view != null) {\n        ui_app_toast_character(utf8);\n    } else {\n        ui_view.character(view, utf8);\n    }\n    ui_app_high_surrogate = 0x0000;\n}\n\nstatic void ui_app_wm_char(ui_view_t* view, const uint16_t* utf16) {\n    int32_t utf16chars = rt_str.len16(utf16);\n    rt_swear(0 < utf16chars && utf16chars < 4); // wParam is 64bits\n    const uint16_t utf16char = utf16[0];\n    if (utf16chars == 1 && rt_str.utf16_is_high_surrogate(utf16char)) {\n        ui_app_high_surrogate = utf16char;\n    } else if (utf16chars == 1 && rt_str.utf16_is_low_surrogate(utf16char)) {\n        if (ui_app_high_surrogate != 0) {\n            uint16_t utf16_surrogate_pair[3] = {\n                ui_app_high_surrogate,\n                utf16char,\n                0x0000\n            };\n            ui_app_dispatch_wm_char(view, utf16_surrogate_pair);\n        }\n    } else {\n        ui_app_dispatch_wm_char(view, utf16);\n    }\n}\n\nstatic bool ui_app_wm_key_pressed(ui_view_t* v, int64_t key) {\n    if (ui_app.animating.view != null) {\n        return ui_app_toast_key_pressed(key);\n    } else {\n        return ui_view.key_pressed(v, key);\n    }\n}\n\nstatic bool ui_app_mouse(ui_view_t* v, int32_t m, int64_t f) {\n    bool swallow = false;\n    // override ui_app_update_mouse_buttons_state() (sic):\n    // because mouse message can be from the past\n    ui_app.mouse_left   = f & (ui_app.mouse_swapped ? MK_RBUTTON : MK_LBUTTON);\n    ui_app.mouse_middle = f & MK_MBUTTON;\n    ui_app.mouse_right  = f & (ui_app.mouse_swapped ? MK_LBUTTON : MK_RBUTTON);\n    ui_view_t* av = ui_app.animating.view;\n    if (m == WM_MOUSEHOVER) {\n        ui_view.mouse_hover(av != null && av->mouse_hover != null ? av : v);\n    } else if (m == WM_MOUSEMOVE) {\n        ui_view.mouse_move(av != null && av->mouse_move != null ? av : v);\n    } else if (m == WM_LBUTTONDOWN  ||\n               m == WM_LBUTTONUP    ||\n               m == WM_MBUTTONDOWN  ||\n               m == WM_MBUTTONUP    ||\n               m == WM_RBUTTONDOWN  ||\n               m == WM_RBUTTONUP) {\n        const int i =\n             (m == WM_LBUTTONDOWN || m == WM_LBUTTONUP) ? 0 :\n            ((m == WM_MBUTTONDOWN || m == WM_MBUTTONUP) ? 1 :\n            ((m == WM_RBUTTONDOWN || m == WM_RBUTTONUP) ? 2 : -1));\n        rt_swear(i >= 0);\n        const int32_t ix = ui_app.mouse_swapped ? 2 - i : i;\n        const bool pressed =\n            m == WM_LBUTTONDOWN ||\n            m == WM_MBUTTONDOWN ||\n            m == WM_RBUTTONDOWN;\n        if (av != null) {\n            // because of \"micro\" close button:\n            swallow = ui_app_toast_tap(ui_app.animating.view, ix, pressed);\n        } else {\n            if (av != null && av->tap != null) {\n                swallow = ui_view.tap(av, ix, pressed);\n            } else {\n                // tap detector will handle the tap() calling\n            }\n        }\n    } else if (m == WM_LBUTTONDBLCLK ||\n               m == WM_MBUTTONDBLCLK ||\n               m == WM_RBUTTONDBLCLK) {\n        const int i =\n             (m == WM_LBUTTONDBLCLK) ? 0 :\n            ((m == WM_MBUTTONDBLCLK) ? 1 :\n            ((m == WM_RBUTTONDBLCLK) ? 2 : -1));\n        rt_swear(i >= 0);\n        if (av != null && av->double_tap != null) {\n            const int32_t ix = ui_app.mouse_swapped ? 2 - i : i;\n            swallow = ui_view.double_tap(av, ix);\n        }\n        // otherwise tap detector will do the double_tap() call\n    } else {\n        rt_assert(false, \"m: 0x%04X\", m);\n    }\n    return swallow;\n}\n\nstatic void ui_app_show_sys_menu(int32_t x, int32_t y) {\n    HMENU sys_menu = GetSystemMenu(ui_app_window(), false);\n    if (sys_menu != null) {\n        // TPM_RIGHTBUTTON means both left and right click to select menu item\n        const DWORD flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON |\n                            TPM_RETURNCMD | TPM_VERPOSANIMATION;\n        int32_t sys_cmd = TrackPopupMenu(sys_menu, flags, x, y, 0,\n                                         ui_app_window(), null);\n        if (sys_cmd != 0) {\n            ui_app_post_message(WM_SYSCOMMAND, sys_cmd, 0);\n        }\n    }\n}\n\nstatic int32_t ui_app_nc_mouse_message(int32_t m) {\n    switch (m) {\n        case WM_NCMOUSEMOVE     : return WM_MOUSEMOVE;\n        case WM_NCLBUTTONDOWN   : return WM_LBUTTONDOWN;\n        case WM_NCLBUTTONUP     : return WM_LBUTTONUP;\n        case WM_NCLBUTTONDBLCLK : return WM_LBUTTONDBLCLK;\n        case WM_NCMBUTTONDOWN   : return WM_MBUTTONDOWN;\n        case WM_NCMBUTTONUP     : return WM_MBUTTONUP;\n        case WM_NCMBUTTONDBLCLK : return WM_MBUTTONDBLCLK;\n        case WM_NCRBUTTONDOWN   : return WM_RBUTTONDOWN;\n        case WM_NCRBUTTONUP     : return WM_RBUTTONUP;\n        case WM_NCRBUTTONDBLCLK : return WM_RBUTTONDBLCLK;\n        default: rt_swear(false, \"fix me m: %d\", m);\n    }\n    return -1;\n}\n\nstatic bool ui_app_nc_mouse_buttons(int32_t m, int64_t wp, int64_t lp) {\n    bool swallow = false;\n    POINT screen = {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)};\n    POINT client = screen;\n    ScreenToClient(ui_app_window(), &client);\n    ui_app.mouse = ui_app_point2ui(&client);\n    const bool inside = ui_view.inside(ui_app.caption, &ui_app.mouse);\n    if (!ui_view.is_hidden(ui_app.caption) && inside) {\n        uint16_t lr = ui_app.mouse_swapped ? WM_NCLBUTTONDOWN : WM_NCRBUTTONDOWN;\n        if (m == lr) {\n//          rt_println(\"WM_NC*BUTTONDOWN %d %d\", ui_app.mouse.x, ui_app.mouse.y);\n            swallow = true;\n            ui_app_show_sys_menu(screen.x, screen.y);\n        }\n    } else {\n        swallow = ui_app_mouse(ui_app.root, ui_app_nc_mouse_message(m), wp);\n    }\n    return swallow;\n}\n\nenum { ui_app_animation_steps = 63 };\n\nstatic void ui_app_toast_paint(void) {\n    static ui_bitmap_t image_dark;\n    if (image_dark.texture == null) {\n        uint8_t pixels[4] = { 0x3F, 0x3F, 0x3F };\n        ui_gdi.bitmap_init(&image_dark, 1, 1, 3, pixels);\n    }\n    static ui_bitmap_t image_light;\n    if (image_dark.texture == null) {\n        uint8_t pixels[4] = { 0xC0, 0xC0, 0xC0 };\n        ui_gdi.bitmap_init(&image_light, 1, 1, 3, pixels);\n    }\n    ui_view_t* av = ui_app.animating.view;\n    if (av != null) {\n        ui_view.measure(av);\n        bool hint = ui_app.animating.x >= 0 && ui_app.animating.y >= 0;\n        const int32_t em_w = av->fm->em.w;\n        const int32_t em_h = av->fm->em.h;\n        if (!hint) {\n            rt_assert(0 <= ui_app.animating.step && ui_app.animating.step < ui_app_animation_steps);\n            int32_t step = ui_app.animating.step - (ui_app_animation_steps - 1);\n            av->y = av->h * step / (ui_app_animation_steps - 1);\n//          rt_println(\"step=%d of %d y=%d\", ui_app.animating.step,\n//                  ui_app_toast_steps, av->y);\n            ui_app_measure_and_layout(av);\n            // dim main window (as `disabled`):\n            fp64_t alpha = rt_min(0.40, 0.40 * ui_app.animating.step / (fp64_t)ui_app_animation_steps);\n            ui_gdi.alpha(0, 0, ui_app.crc.w, ui_app.crc.h,\n                         0, 0, image_dark.w, image_dark.h,\n                        &image_dark, alpha);\n            av->x = (ui_app.root->w - av->w) / 2;\n//          rt_println(\"ui_app.animating.y: %d av->y: %d\",\n//                  ui_app.animating.y, av->y);\n        } else {\n            av->x = ui_app.animating.x;\n            av->y = ui_app.animating.y;\n            ui_app_measure_and_layout(av);\n            int32_t mx = ui_app.root->w - av->w - em_w;\n            int32_t cx = ui_app.animating.x - av->w / 2;\n            av->x = rt_min(mx, rt_max(0, cx));\n            av->y = rt_min(\n                ui_app.root->h - em_h, rt_max(0, ui_app.animating.y));\n//          rt_println(\"ui_app.animating.y: %d av->y: %d\",\n//                  ui_app.animating.y, av->y);\n        }\n        int32_t x = av->x - em_w / 4;\n        int32_t y = av->y - em_h / 8;\n        int32_t w = av->w + em_w / 2;\n        int32_t h = av->h + em_h / 4;\n        int32_t radius = em_w / 2;\n        if (radius % 2 == 0) { radius++; }\n        ui_color_t color = ui_theme.is_app_dark() ?\n            ui_color_rgb(45, 45, 48) : // TODO: hard coded\n            ui_colors.get_color(ui_color_id_button_face);\n        ui_color_t tint = ui_colors.interpolate(color, ui_colors.yellow, 0.5f);\n        ui_gdi.rounded(x, y, w, h, radius, tint, tint);\n        if (!hint) { av->y += em_h / 4; }\n        ui_app_paint(av);\n        if (!hint) {\n            if (av->y == em_h / 4) {\n                // micro \"close\" toast button:\n                int32_t r = av->x + av->w;\n                const int32_t tx = r - em_w / 2;\n                const int32_t ty = 0;\n                const ui_gdi_ta_t ta = {\n                    .fm = &ui_app.fm.prop.normal,\n                    .color = ui_color_undefined,\n                    .color_id = ui_color_id_window_text\n                };\n                ui_gdi.text(&ta, tx, ty, \"%s\",\n                                 rt_glyph_multiplication_sign);\n            }\n        }\n    }\n}\n\nstatic void ui_app_toast_cancel(void) {\n    if (ui_app.animating.view != null) {\n        if (ui_app.animating.view->type == ui_view_mbx) {\n            ui_mbx_t* mx = (ui_mbx_t*)ui_app.animating.view;\n            if (mx->option < 0 && mx->callback != null) {\n                mx->callback(&mx->view);\n            }\n        }\n        ui_app.animating.view->parent = null;\n        ui_app.animating.step = 0;\n        ui_app.animating.view = null;\n        ui_app.animating.time = 0;\n        ui_app.animating.x = -1;\n        ui_app.animating.y = -1;\n        if (ui_app.animating.focused != null) {\n            ui_view.set_focus(ui_app.animating.focused->focusable &&\n               !ui_view.is_hidden(ui_app.animating.focused) &&\n               !ui_view.is_disabled(ui_app.animating.focused) ?\n                ui_app.animating.focused : null);\n            ui_app.animating.focused = null;\n        } else {\n            ui_view.set_focus(null);\n        }\n        ui_app.request_redraw();\n    }\n}\n\nstatic bool ui_app_toast_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false;\n    rt_swear(v == ui_app.animating.view);\n    if (pressed) {\n        const ui_fm_t* fm = v->fm;\n        const int32_t right = v->x + v->w;\n        const int32_t x = right - fm->em.w / 2;\n        const int32_t mx = ui_app.mouse.x;\n        const int32_t my = ui_app.mouse.y;\n        // micro close button which is not a button\n        if (x <= mx && mx <= x + fm->em.w && 0 <= my && my <= fm->em.h) {\n            ui_app_toast_cancel();\n        }\n    }\n    if (ui_app.animating.view != null) { // could have been canceled above\n        swallow = ui_view.tap(v, ix, pressed); // TODO: do we need it?\n    }\n    return swallow;\n}\n\nstatic void ui_app_toast_character(const char* utf8) {\n    char ch = utf8[0];\n    if (ui_app.animating.view != null && ch == 033) { // ESC traditionally in octal\n        ui_app_toast_cancel();\n        ui_app.show_toast(null, 0);\n    } else {\n        ui_view.character(ui_app.animating.view, utf8);\n    }\n}\n\nstatic bool ui_app_toast_key_pressed(int64_t key) {\n    if (ui_app.animating.view != null && key == 033) { // ESC traditionally in octal\n        ui_app_toast_cancel();\n        ui_app.show_toast(null, 0);\n        return true;\n    } else {\n        return ui_view.key_pressed(ui_app.animating.view, key);\n    }\n}\n\nstatic void ui_app_toast_dim(int32_t step) {\n    ui_app.animating.step = step;\n    ui_app.request_redraw();\n    UpdateWindow(ui_app_window());\n}\n\nstatic void ui_app_animate_step(ui_app_animate_function_t f, int32_t step, int32_t steps) {\n    // calls function(0..step-1) exactly step times\n    bool cancel = false;\n    if (f != null && f != ui_app_animate.f && step == 0 && steps > 0) {\n        // start animated_groot\n        ui_app_animate.count = steps;\n        ui_app_animate.f = f;\n        f(step);\n        ui_app_animate.timer = ui_app.set_timer((uintptr_t)&ui_app_animate.timer, 10);\n    } else if (f != null && ui_app_animate.f == f && step > 0) {\n        cancel = step >= ui_app_animate.count;\n        if (!cancel) {\n            ui_app_animate.step = step;\n            f(step);\n        }\n    } else if (f == null) {\n        cancel = true;\n    }\n    if (cancel) {\n        if (ui_app_animate.timer != 0) {\n            ui_app.kill_timer(ui_app_animate.timer);\n        }\n        ui_app_animate.step = 0;\n        ui_app_animate.timer = 0;\n        ui_app_animate.f = null;\n        ui_app_animate.count = 0;\n    }\n}\n\nstatic void ui_app_animate_start(ui_app_animate_function_t f, int32_t steps) {\n    // calls f(0..step-1) exactly steps times, unless cancelled with call\n    // animate(null, 0) or animate(other_function, n > 0)\n    ui_app_animate_step(f, 0, steps);\n}\n\nstatic void ui_app_view_paint(ui_view_t* v) {\n    v->color = ui_colors.get_color(v->color_id);\n    if (v->background_id > 0) {\n        v->background = ui_colors.get_color(v->background_id);\n    }\n    if (!ui_color_is_undefined(v->background) &&\n        !ui_color_is_transparent(v->background)) {\n        ui_gdi.fill(v->x, v->y, v->w, v->h, v->background);\n    }\n}\n\nstatic void ui_app_view_layout(void) {\n    rt_not_null(ui_app.window);\n    rt_not_null(ui_app.canvas);\n    if (ui_app.no_decor) {\n        ui_app.root->x = ui_app.border.w;\n        ui_app.root->y = ui_app.border.h;\n        ui_app.root->w = ui_app.crc.w - ui_app.border.w * 2;\n        ui_app.root->h = ui_app.crc.h - ui_app.border.h * 2;\n    } else {\n        ui_app.root->x = 0;\n        ui_app.root->y = 0;\n        ui_app.root->w = ui_app.crc.w;\n        ui_app.root->h = ui_app.crc.h;\n    }\n    ui_app_measure_and_layout(ui_app.root);\n}\n\nstatic void ui_app_view_active_frame_paint(void) {\n    ui_color_t c = ui_app.is_active() ?\n        ui_colors.get_color(ui_color_id_highlight) : // ui_colors.btn_hover_highlight\n        ui_colors.get_color(ui_color_id_inactive_title);\n    rt_assert(ui_app.border.w == ui_app.border.h);\n    const int32_t w = ui_app.wrc.w;\n    const int32_t h = ui_app.wrc.h;\n    for (int32_t i = 0; i < ui_app.border.w; i++) {\n        ui_gdi.frame(i, i, w - i * 2, h - i * 2, c);\n    }\n}\n\nstatic void ui_app_paint_stats(void) {\n    if (ui_app.paint_count % 128 == 0) { ui_app.paint_max = 0; }\n    ui_app.paint_time = rt_clock.seconds() - ui_app.now;\n    ui_app.paint_max = rt_max(ui_app.paint_time, ui_app.paint_max);\n    if (ui_app.paint_avg == 0) {\n        ui_app.paint_avg = ui_app.paint_time;\n    } else { // EMA over 32 paint() calls\n        ui_app.paint_avg = ui_app.paint_avg * (1.0 - 1.0 / 32.0) +\n                        ui_app.paint_time / 32.0;\n    }\n    static fp64_t first_paint;\n    if (first_paint == 0) { first_paint = ui_app.now; }\n    fp64_t since_first_paint = ui_app.now - first_paint;\n    if (since_first_paint > 0) {\n        double fps = (double)ui_app.paint_count / since_first_paint;\n        if (ui_app.paint_fps == 0) {\n            ui_app.paint_fps = fps;\n        } else {\n            ui_app.paint_fps = ui_app.paint_fps * (1.0 - 1.0 / 32.0) + fps / 32.0;\n        }\n    }\n    if (ui_app.paint_last == 0) {\n        ui_app.paint_dt_min = 1.0 / 60.0; // 60Hz monitor\n    } else {\n        fp64_t since_last = ui_app.now - ui_app.paint_last;\n        if (since_last > 1.0 / 120.0) { // 240Hz monitor\n            ui_app.paint_dt_min = rt_min(ui_app.paint_dt_min, since_last);\n        }\n//      rt_println(\"paint_dt_min: %.6f since_last: %.6f\",\n//              ui_app.paint_dt_min, since_last);\n    }\n    ui_app.paint_last = ui_app.now;\n}\n\nstatic void ui_app_paint_on_canvas(HDC hdc) {\n    ui_canvas_t canvas = ui_app.canvas;\n    ui_app.canvas = (ui_canvas_t)hdc;\n    ui_app_update_crc();\n    if (ui_app_layout_dirty) {\n        ui_app_view_layout();\n    }\n    ui_gdi.begin(null);\n    ui_app_paint(ui_app.root);\n    if (ui_app.animating.view != null) { ui_app_toast_paint(); }\n    // active frame on top of everything:\n    if (ui_app.no_decor && !ui_app.is_full_screen &&\n        !ui_app.is_maximized()) {\n        ui_app_view_active_frame_paint();\n    }\n    ui_gdi.end();\n    ui_app.paint_count++;\n    ui_app.canvas = canvas;\n    ui_app_paint_stats();\n}\n\nstatic void ui_app_wm_paint(void) {\n    // it is possible to receive WM_PAINT when window is not closed\n    if (ui_app.window != null) {\n        PAINTSTRUCT ps = {0};\n        BeginPaint(ui_app_window(), &ps);\n        ui_app.prc = ui_app_rect2ui(&ps.rcPaint);\n//      rt_println(\"%d,%d %dx%d\", ui_app.prc.x, ui_app.prc.y, ui_app.prc.w, ui_app.prc.h);\n        ui_app_paint_on_canvas(ps.hdc);\n        EndPaint(ui_app_window(), &ps);\n    }\n}\n\n// about (x,y) being (-32000,-32000) see:\n// https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/ui/views/win/hwnd_message_handler.cc#1847\n\nstatic void ui_app_window_position_changed(const WINDOWPOS* wp) {\n    ui_app.root->state.hidden = !IsWindowVisible(ui_app_window());\n    const bool moved  = (wp->flags & SWP_NOMOVE) == 0;\n    const bool sized  = (wp->flags & SWP_NOSIZE) == 0;\n    const bool hiding = (wp->flags & SWP_HIDEWINDOW) != 0 ||\n                        (wp->x == -32000 && wp->y == -32000);\n    HMONITOR monitor = MonitorFromWindow(ui_app_window(), MONITOR_DEFAULTTONULL);\n    if (!ui_app.root->state.hidden && (moved || sized) &&\n        !hiding && monitor != null) {\n        RECT wrc = ui_app_ui2rect(&ui_app.wrc);\n        rt_fatal_win32err(GetWindowRect(ui_app_window(), &wrc));\n        ui_app.wrc = ui_app_rect2ui(&wrc);\n        ui_app_update_mi(&ui_app.wrc, MONITOR_DEFAULTTONEAREST);\n        ui_app_update_crc();\n        if (ui_app_timer_1s_id != 0) { ui_app.request_layout(); }\n    }\n}\n\nstatic void ui_app_setting_change(uintptr_t wp, uintptr_t lp) {\n    // wp: SPI_SETWORKAREA ... SPI_SETDOCKMOVING\n    //     SPI_GETACTIVEWINDOWTRACKING ... SPI_SETGESTUREVISUALIZATION\n    if (wp == SPI_SETLOGICALDPIOVERRIDE) {\n        ui_app_init_fonts(ui_app.dpi.window); // font scale changed\n        ui_app.request_layout();\n    } else if (lp != 0 &&\n       (strcmp((const char*)lp, \"ImmersiveColorSet\") == 0 ||\n        wcscmp((const uint16_t*)lp, L\"ImmersiveColorSet\") == 0)) {\n        // expected:\n        // SPI_SETICONTITLELOGFONT 0x22 ?\n        // SPI_SETNONCLIENTMETRICS 0x2A ?\n//      rt_println(\"wp: 0x%08X\", wp);\n        // actual wp == 0x0000\n        ui_theme.refresh();\n    } else if (wp == 0 && lp != 0 && strcmp((const char*)lp, \"intl\") == 0) {\n        rt_println(\"wp: 0x%04X\", wp); // SPI_SETLOCALEINFO 0x24 ?\n        uint16_t ln[LOCALE_NAME_MAX_LENGTH + 1];\n        int32_t n = GetUserDefaultLocaleName(ln, rt_countof(ln));\n        rt_fatal_if(n <= 0);\n        uint16_t rln[LOCALE_NAME_MAX_LENGTH + 1];\n        n = ResolveLocaleName(ln, rln, rt_countof(rln));\n        rt_fatal_if(n <= 0);\n        LCID lc_id = LocaleNameToLCID(rln, LOCALE_ALLOW_NEUTRAL_NAMES);\n        rt_fatal_win32err(SetThreadLocale(lc_id));\n    }\n}\n\nstatic void ui_app_show_task_bar(bool show) {\n    HWND taskbar = FindWindowA(\"Shell_TrayWnd\", null);\n    if (taskbar != null) {\n        ShowWindow(taskbar, show ? SW_SHOW : SW_HIDE);\n        UpdateWindow(taskbar);\n    }\n}\n\nstatic bool ui_app_click_detector(uint32_t msg, WPARAM wp, LPARAM lp) {\n    bool swallow = false;\n    enum { tap = 1, long_press = 2, double_tap = 3 };\n    // TODO: click detector does not handle WM_NCLBUTTONDOWN, ...\n    //       it can be modified to do so if needed\n    #pragma push_macro(\"ui_set_timer\")\n    #pragma push_macro(\"ui_kill_timer\")\n    #pragma push_macro(\"ui_timers_done\")\n\n    #define ui_set_timer(t, ms) do {                 \\\n        rt_assert(t == 0);                           \\\n        t = ui_app_timer_set((uintptr_t)&t, ms);     \\\n    } while (0)\n\n    #define ui_kill_timer(t) do {                    \\\n        if (t != 0) { ui_app_timer_kill(t); t = 0; } \\\n    } while (0)\n\n    #define ui_timers_done(ix) do {                  \\\n        clicked[ix] = 0;                             \\\n        pressed[ix] = false;                         \\\n        click_at[ix] = (ui_point_t){0, 0};           \\\n        ui_kill_timer(timer_p[ix]);                  \\\n        ui_kill_timer(timer_d[ix]);                  \\\n    } while (0)\n\n    // This function should work regardless to CS_BLKCLK being present\n    // 0: Left, 1: Middle, 2: Right\n    static ui_point_t click_at[3];\n    static fp64_t     clicked[3]; // click time\n    static bool       pressed[3];\n    static ui_timer_t timer_d[3]; // double tap\n    static ui_timer_t timer_p[3]; // long press\n    bool up = false;\n    int32_t ix = -1;\n    int32_t m = 0;\n    switch (msg) {\n        case WM_LBUTTONDOWN  : ix = 0; m = tap;        break;\n        case WM_MBUTTONDOWN  : ix = 1; m = tap;        break;\n        case WM_RBUTTONDOWN  : ix = 2; m = tap;        break;\n        case WM_LBUTTONDBLCLK: ix = 0; m = double_tap; break;\n        case WM_MBUTTONDBLCLK: ix = 1; m = double_tap; break;\n        case WM_RBUTTONDBLCLK: ix = 2; m = double_tap; break;\n        case WM_LBUTTONUP    : ix = 0; m = tap; up = true; break;\n        case WM_MBUTTONUP    : ix = 1; m = tap; up = true; break;\n        case WM_RBUTTONUP    : ix = 2; m = tap; up = true; break;\n    }\n    if (msg == WM_TIMER) { // long press && double tap\n        for (int i = 0; i < 3; i++) {\n            if (wp == timer_p[i]) {\n                ui_app.mouse = (ui_point_t){ click_at[i].x, click_at[i].y };\n                ui_view.long_press(ui_app.root, i);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", i);\n                ui_timers_done(i);\n            }\n            if (wp == timer_d[i]) {\n//              rt_println(\"timer_p[%d] _d && _p timers done\", i);\n                ui_timers_done(i);\n            }\n        }\n    }\n    if (ix != -1) {\n        ui_app.show_hint(null, -1, -1, 0); // dismiss hint on any click\n        const int32_t double_click_msec = (int32_t)GetDoubleClickTime();\n        const fp64_t  double_click_dt = double_click_msec / 1000.0; // seconds\n//      rt_println(\"double_click_msec: %d double_click_dt: %.3fs\",\n//               double_click_msec, double_click_dt);\n        const int double_click_x = GetSystemMetrics(SM_CXDOUBLECLK) / 2;\n        const int double_click_y = GetSystemMetrics(SM_CYDOUBLECLK) / 2;\n        ui_point_t pt = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };\n        if (m == tap && !up) {\n            swallow = ui_view.tap(ui_app.root, ix, !up);\n            if (ui_app.now  - clicked[ix]  <= double_click_dt &&\n                abs(pt.x - click_at[ix].x) <= double_click_x &&\n                abs(pt.y - click_at[ix].y) <= double_click_y) {\n                ui_app.mouse = (ui_point_t){ click_at[ix].x, click_at[ix].y };\n                ui_view.double_tap(ui_app.root, ix);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix);\n            } else {\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix); // clear timers\n                clicked[ix]  = ui_app.now;\n                click_at[ix] = pt;\n                pressed[ix]  = true;\n//              rt_println(\"clicked[%d] := %.1f %d,%d pressed[%d] := true\",\n//                          ix, clicked[ix], pt.x, pt.y, ix);\n                if ((ui_app_wc.style & CS_DBLCLKS) == 0) {\n                    // only if Windows are not detecting DLBCLKs\n//                  rt_println(\"ui_set_timer(timer_d[%d])\", ix);\n                    ui_set_timer(timer_d[ix], double_click_msec);  // 0.5s\n                }\n                ui_set_timer(timer_p[ix], double_click_msec * 3 / 4); // 0.375s\n            }\n        } else if (up) {\n            fp64_t since_clicked = ui_app.now - clicked[ix];\n//          rt_println(\"pressed[%d]: %d %.3f\", ix, pressed[ix], since_clicked);\n            // only if Windows are not detecting DLBCLKs\n            if ((ui_app_wc.style & CS_DBLCLKS) == 0 &&\n                 pressed[ix] && since_clicked > double_click_dt) {\n                ui_view.double_tap(ui_app.root, ix);\n//              rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n                ui_timers_done(ix);\n            }\n            swallow = ui_view.tap(ui_app.root, ix, !up);\n            ui_kill_timer(timer_p[ix]); // long press is not the case\n        } else if (m == double_tap) {\n            rt_assert((ui_app_wc.style & CS_DBLCLKS) != 0);\n            swallow = ui_view.double_tap(ui_app.root, ix);\n            ui_timers_done(ix);\n//          rt_println(\"timer_p[%d] _d && _p timers done\", ix);\n        }\n    }\n    #pragma pop_macro(\"ui_timers_done\")\n    #pragma pop_macro(\"ui_kill_timer\")\n    #pragma pop_macro(\"ui_set_timer\")\n    return swallow;\n}\n\nstatic int64_t ui_app_root_hit_test(const ui_view_t* v, ui_point_t pt) {\n    rt_swear(v == ui_app.root);\n    if (ui_app.no_decor) {\n        rt_assert(ui_app.border.w == ui_app.border.h);\n        // on 96dpi monitors ui_app.border is 1x1\n        // make it easier for the user to resize window\n        int32_t border = rt_max(4, ui_app.border.w * 2);\n        if (ui_app.animating.view != null) {\n            return ui.hit_test.client; // message box or toast is up\n        } else if (!ui_view.is_hidden(&ui_caption.view) &&\n                    ui_view.inside(&ui_caption.view, &pt)) {\n            return ui_caption.view.hit_test(&ui_caption.view, pt);\n        } else if (ui_app.is_maximized()) {\n            int64_t ht = ui_view.hit_test(ui_app.content, pt);\n            return ht == ui.hit_test.nowhere ? ui.hit_test.client : ht;\n        } else if (ui_app.is_full_screen) {\n            return ui.hit_test.client;\n        } else if (pt.x < border && pt.y < border) {\n            return ui.hit_test.top_left;\n        } else if (pt.x > ui_app.crc.w - border && pt.y < border) {\n            return ui.hit_test.top_right;\n        } else if (pt.y < border) {\n            return ui.hit_test.top;\n        } else if (pt.x > ui_app.crc.w - border &&\n                   pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom_right;\n        } else if (pt.x < border && pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom_left;\n        } else if (pt.x < border) {\n            return ui.hit_test.left;\n        } else if (pt.x > ui_app.crc.w - border) {\n            return ui.hit_test.right;\n        } else if (pt.y > ui_app.crc.h - border) {\n            return ui.hit_test.bottom;\n        } else {\n            // drop down to content hit test\n        }\n    }\n    return ui.hit_test.nowhere;\n}\n\nstatic void ui_app_wm_activate(int64_t wp) {\n    bool activate = LOWORD(wp) != WA_INACTIVE;\n    if (!IsWindowVisible(ui_app_window()) && activate) {\n        ui_app.show_window(ui.visibility.restore);\n        SwitchToThisWindow(ui_app_window(), true);\n    }\n    ui_app.request_redraw(); // needed for windows changing active frame color\n}\n\nstatic void ui_app_update_mouse_buttons_state(void) {\n    ui_app.mouse_swapped = GetSystemMetrics(SM_SWAPBUTTON) != 0;\n    ui_app.mouse_left  = (GetAsyncKeyState(ui_app.mouse_swapped ?\n                          VK_RBUTTON : VK_LBUTTON) & 0x8000) != 0;\n    ui_app.mouse_right = (GetAsyncKeyState(ui_app.mouse_swapped ?\n                          VK_LBUTTON : VK_RBUTTON) & 0x8000) != 0;\n}\n\nstatic int64_t ui_app_wm_nc_hit_test(int64_t wp, int64_t lp) {\n    ui_point_t pt = { GET_X_LPARAM(lp) - ui_app.wrc.x,\n                      GET_Y_LPARAM(lp) - ui_app.wrc.y };\n    int64_t ht = ui_view.hit_test(ui_app.root, pt);\n    if (ht != ui.hit_test.nowhere) {\n        return ht;\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_NCHITTEST, wp, lp);\n    }\n}\n\nstatic int64_t ui_app_wm_sys_key_down(int64_t wp, int64_t lp) {\n    ui_app_alt_ctrl_shift(true, wp);\n    if (ui_app_wm_key_pressed(ui_app.root, wp) || wp == VK_MENU) {\n        return 0; // no DefWindowProcW()\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_SYSKEYDOWN, wp, lp);\n    }\n}\n\nstatic void ui_app_wm_set_focus(void) {\n    if (!ui_app.root->state.hidden) {\n        rt_assert(GetActiveWindow() == ui_app_window());\n        if (ui_app.focus != null && ui_app.focus->focus_lost != null) {\n            ui_app.focus->focus_gained(ui_app.focus);\n        }\n    }\n}\n\nstatic void ui_app_wm_kill_focus(void) {\n    if (!ui_app.root->state.hidden &&\n        ui_app.focus != null &&\n        ui_app.focus->focus_lost != null) {\n        ui_app.focus->focus_lost(ui_app.focus);\n    }\n}\n\nstatic int64_t ui_app_wm_nc_calculate_size(int64_t wp, int64_t lp) {\n//  NCCALCSIZE_PARAMS* szp = (NCCALCSIZE_PARAMS*)lp;\n//  rt_println(\"WM_NCCALCSIZE wp: %lld is_max: %d (%d %d %d %d) (%d %d %d %d) (%d %d %d %d)\",\n//      wp, ui_app.is_maximized(),\n//      szp->rgrc[0].left, szp->rgrc[0].top, szp->rgrc[0].right, szp->rgrc[0].bottom,\n//      szp->rgrc[1].left, szp->rgrc[1].top, szp->rgrc[1].right, szp->rgrc[1].bottom,\n//      szp->rgrc[2].left, szp->rgrc[2].top, szp->rgrc[2].right, szp->rgrc[2].bottom);\n    // adjust window client area frame for no_decor windows\n    if (wp == true && ui_app.no_decor && !ui_app.is_maximized()) {\n        return 0;\n    } else {\n        return DefWindowProcW(ui_app_window(), WM_NCCALCSIZE, wp, lp);\n    }\n}\n\nstatic int64_t ui_app_wm_get_dpi_scaled_size(int64_t wp) {\n    // sent before WM_DPICHANGED\n    #ifdef UI_APP_DEBUG\n        int32_t dpi = wp;\n        SIZE* sz = (SIZE*)lp; // in/out\n        ui_point_t cell = { sz->cx, sz->cy };\n        rt_println(\"WM_GETDPISCALEDSIZE dpi %d := %d \"\n            \"size %d,%d *may/must* be adjusted\",\n            ui_app.dpi.window, dpi, cell.x, cell.y);\n    #else\n        (void)wp; // unused\n    #endif\n    if (ui_app_timer_1s_id != 0 && !ui_app.root->state.hidden) {\n        ui_app.request_layout();\n    }\n    // IMPORTANT: return true because:\n    // \"Returning TRUE indicates that a new size has been computed.\n    //  Returning FALSE indicates that the message will not be handled,\n    //  and the default linear DPI scaling will apply to the window.\"\n    // https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-getdpiscaledsize\n    return true;\n}\n\nstatic void ui_app_wm_dpi_changed(void) {\n    ui_app_window_dpi();\n    ui_app_init_fonts(ui_app.dpi.window);\n    if (ui_app_timer_1s_id != 0 && !ui_app.root->state.hidden) {\n        ui_app.request_layout();\n    } else {\n        ui_app_layout_dirty = true;\n    }\n}\n\nstatic bool ui_app_wm_sys_command(int64_t wp, int64_t lp) {\n    uint16_t sys_cmd = (uint16_t)(wp & 0xFF0);\n//  rt_println(\"WM_SYSCOMMAND wp: 0x%08llX lp: 0x%016llX %lld sys: 0x%04X\",\n//          wp, lp, lp, sys_cmd);\n    if (sys_cmd == SC_MINIMIZE && ui_app.hide_on_minimize) {\n        ui_app.show_window(ui.visibility.min_na);\n        ui_app.show_window(ui.visibility.hide);\n    } else  if (sys_cmd == SC_MINIMIZE && ui_app.no_decor) {\n        ui_app.show_window(ui.visibility.min_na);\n    }\n//  if (sys_cmd == SC_KEYMENU) { rt_println(\"SC_KEYMENU lp: %lld\", lp); }\n    // If the selection is in menu handle the key event\n    if (sys_cmd == SC_KEYMENU && lp != 0x20) {\n        return true; // handled: This prevents the error/beep sound\n    }\n    if (sys_cmd == SC_MAXIMIZE && ui_app.no_decor) {\n        return true; // handled: prevent maximizing no decorations window\n    }\n//  if (sys_cmd == SC_MOUSEMENU) {\n//      rt_println(\"SC_KEYMENU.SC_MOUSEMENU 0x%00llX %lld\", wp, lp);\n//  }\n    return false; // drop down to to DefWindowProc\n}\n\nstatic void ui_app_wm_window_position_changing(int64_t wp, int64_t lp) {\n    #ifdef UI_APP_DEBUG // TODO: ui_app.debug.trace.window_position?\n        WINDOWPOS* pos = (WINDOWPOS*)lp;\n        rt_println(\"WM_WINDOWPOSCHANGING flags: 0x%08X\", pos->flags);\n        if (pos->flags & SWP_SHOWWINDOW) {\n            rt_println(\"SWP_SHOWWINDOW\");\n        } else if (pos->flags & SWP_HIDEWINDOW) {\n            rt_println(\"SWP_HIDEWINDOW\");\n        }\n    #else\n        (void)wp; // unused\n        (void)lp; // unused\n    #endif\n}\n\nstatic bool ui_app_wm_mouse(int32_t m, int64_t wp, int64_t lp) {\n    // note: x, y is already in client coordinates\n    ui_app.mouse.x = GET_X_LPARAM(lp);\n    ui_app.mouse.y = GET_Y_LPARAM(lp);\n    return ui_app_mouse(ui_app.root, m, wp);\n}\n\nstatic void ui_app_wm_mouse_wheel(bool vertical, int64_t wp) {\n    if (vertical) {\n        ui_point_t dx_dy = { 0, GET_WHEEL_DELTA_WPARAM(wp) };\n        ui_view.mouse_scroll(ui_app.root, dx_dy);\n    } else {\n        ui_point_t dx_dy = { GET_WHEEL_DELTA_WPARAM(wp), 0 };\n        ui_view.mouse_scroll(ui_app.root, dx_dy);\n    }\n}\n\nstatic void ui_app_wm_input_language_change(uint64_t wp) {\n    #ifdef UI_APP_TRACE_WM_INPUT_LANGUAGE_CHANGE\n    static struct { uint8_t charset; const char* name; } cs[] = {\n        { ANSI_CHARSET       ,     \"ANSI_CHARSET       \" },\n        { DEFAULT_CHARSET    ,     \"DEFAULT_CHARSET    \" },\n        { SYMBOL_CHARSET     ,     \"SYMBOL_CHARSET     \" },\n        { MAC_CHARSET        ,     \"MAC_CHARSET        \" },\n        { SHIFTJIS_CHARSET   ,     \"SHIFTJIS_CHARSET   \" },\n        { HANGEUL_CHARSET    ,     \"HANGEUL_CHARSET    \" },\n        { HANGUL_CHARSET     ,     \"HANGUL_CHARSET     \" },\n        { GB2312_CHARSET     ,     \"GB2312_CHARSET     \" },\n        { CHINESEBIG5_CHARSET,     \"CHINESEBIG5_CHARSET\" },\n        { OEM_CHARSET        ,     \"OEM_CHARSET        \" },\n        { JOHAB_CHARSET      ,     \"JOHAB_CHARSET      \" },\n        { HEBREW_CHARSET     ,     \"HEBREW_CHARSET     \" },\n        { ARABIC_CHARSET     ,     \"ARABIC_CHARSET     \" },\n        { GREEK_CHARSET      ,     \"GREEK_CHARSET      \" },\n        { TURKISH_CHARSET    ,     \"TURKISH_CHARSET    \" },\n        { VIETNAMESE_CHARSET ,     \"VIETNAMESE_CHARSET \" },\n        { THAI_CHARSET       ,     \"THAI_CHARSET       \" },\n        { EASTEUROPE_CHARSET ,     \"EASTEUROPE_CHARSET \" },\n        { RUSSIAN_CHARSET    ,     \"RUSSIAN_CHARSET    \" },\n        { BALTIC_CHARSET     ,     \"BALTIC_CHARSET     \" }\n    };\n    for (int32_t i = 0; i < rt_countof(cs); i++) {\n        if (cs[i].charset == wp) {\n            rt_println(\"WM_INPUTLANGCHANGE: 0x%08X %s\", wp, cs[i].name);\n            break;\n        }\n    }\n    #else\n        (void)wp; // unused\n    #endif\n}\n\nstatic void ui_app_decode_keyboard(int32_t m, int64_t wp, int64_t lp) {\n    // https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags\n    rt_swear(m == WM_KEYDOWN || m == WM_SYSKEYDOWN ||\n          m == WM_KEYUP   || m == WM_SYSKEYUP);\n    uint16_t vk_code   = LOWORD(wp);\n    uint16_t key_flags = HIWORD(lp);\n    uint16_t scan_code = LOBYTE(key_flags);\n    if ((key_flags & KF_EXTENDED) == KF_EXTENDED) {\n        scan_code = MAKEWORD(scan_code, 0xE0);\n    }\n    // previous key-state flag, 1 on autorepeat\n    bool was_key_down = (key_flags & KF_REPEAT) == KF_REPEAT;\n    // repeat count, > 0 if several key down messages was combined into one\n    uint16_t repeat_count = LOWORD(lp);\n    // transition-state flag, 1 on key up\n    bool is_key_released = (key_flags & KF_UP) == KF_UP;\n    // if we want to distinguish these keys:\n    switch (vk_code) {\n        case VK_SHIFT:   // converts to VK_LSHIFT or VK_RSHIFT\n        case VK_CONTROL: // converts to VK_LCONTROL or VK_RCONTROL\n        case VK_MENU:    // converts to VK_LMENU or VK_RMENU\n            vk_code = LOWORD(MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX));\n            break;\n        default: break;\n    }\n    static BYTE keyboard_state[256];\n    uint16_t utf16[3] = {0};\n    rt_fatal_win32err(GetKeyboardState(keyboard_state));\n    // HKL low word Language Identifier\n    //     high word device handle to the physical layout of the keyboard\n    const HKL kl = GetKeyboardLayout(0);\n    // Map virtual key to scan code\n    UINT vk = MapVirtualKeyEx(scan_code, MAPVK_VSC_TO_VK_EX, kl);\n//  rt_println(\"virtual_key: %02X keyboard layout: %08X\",\n//              virtual_key, kl);\n    memset(ui_app_decoded_released, 0x00, sizeof(ui_app_decoded_released));\n    memset(ui_app_decoded_pressed,  0x00, sizeof(ui_app_decoded_pressed));\n    // Translate scan code to character\n    int32_t r = ToUnicodeEx(vk, scan_code, keyboard_state,\n                            utf16, rt_countof(utf16), 0, kl);\n    if (r > 0) {\n        rt_static_assertion(rt_countof(ui_app_decoded_pressed) ==\n                            rt_countof(ui_app_decoded_released));\n        enum { capacity = (int32_t)rt_countof(ui_app_decoded_released) };\n        char* utf8 = is_key_released ?\n            ui_app_decoded_released : ui_app_decoded_pressed;\n        rt_str.utf16to8(utf8, capacity, utf16, -1);\n        if (ui_app_trace_utf16_keyboard_input) {\n            rt_println(\"0x%04X%04X released: %d down: %d repeat: %d \\\"%s\\\"\",\n                    utf16[0], utf16[1], is_key_released, was_key_down,\n                    repeat_count, utf8);\n        }\n    } else if (r == 0) {\n        // The specified virtual key has no translation for the\n        // current state of the keyboard. (E.g. arrows, enter etc)\n    } else {\n        rt_assert(r < 0);\n        // The specified virtual key is a dead key character (accent or diacritic).\n        if (ui_app_trace_utf16_keyboard_input) { rt_println(\"dead key\"); }\n    }\n}\n\nstatic void ui_app_ime_composition(int64_t lp) {\n    if (lp & GCS_RESULTSTR) {\n        HIMC imc = ImmGetContext(ui_app_window());\n        if (imc != null) {\n            char utf8[16];\n            uint16_t utf16[4] = {0};\n            uint32_t bytes = ImmGetCompositionStringW(imc, GCS_RESULTSTR, null, 0);\n            uint32_t count = bytes / sizeof(uint16_t);\n            if (0 < count && count < rt_countof(utf16) - 1) {\n                ImmGetCompositionStringW(imc, GCS_RESULTSTR, utf16, bytes);\n                utf16[count] = 0x00;\n                rt_str.utf16to8(utf8, rt_countof(utf8), utf16, -1);\n                rt_println(\"bytes: %d 0x%04X 0x%04X %s\", bytes, utf16[0], utf16[1], utf8);\n            }\n            rt_fatal_win32err(ImmReleaseContext(ui_app_window(), imc));\n        }\n    }\n}\n\nstatic LRESULT CALLBACK ui_app_window_proc(HWND window, UINT message,\n        WPARAM w_param, LPARAM l_param) {\n    ui_app.now = rt_clock.seconds();\n    if (ui_app.window == null) {\n        ui_app.window = (ui_window_t)window;\n    } else {\n        rt_assert(ui_app_window() == window);\n    }\n    rt_work_queue.dispatch(&ui_app_queue);\n    ui_app_update_wt_timeout(); // because head might have changed\n    const int32_t m  = (int32_t)message;\n    const int64_t wp = (int64_t)w_param;\n    const int64_t lp = (int64_t)l_param;\n    int64_t ret = 0;\n    ui_app_update_mouse_buttons_state();\n    ui_view.lose_hidden_focus(ui_app.root);\n    if (ui_app_click_detector((uint32_t)m, (WPARAM)wp, (LPARAM)lp)) {\n        return 0;\n    }\n    if (ui_view.message(ui_app.root, m, wp, lp, &ret)) {\n        return (LRESULT)ret;\n    }\n    if (m == ui.message.opening) { ui_app_window_opening(); return 0; }\n    if (m == ui.message.closing) { ui_app_window_closing(); return 0; }\n    if (m == ui.message.animate) {\n        ui_app_animate_step((ui_app_animate_function_t)lp, (int32_t)wp, -1);\n        return 0;\n    }\n    ui_app_message_handler_t* handler = ui_app.handlers; \n    while (handler != null) { \n        if (handler->callback(handler, m, wp, lp, &ret)) {\n            return ret;\n        }\n        handler = handler->next;\n    }\n    switch (m) {\n        case WM_GETMINMAXINFO:\n            ui_app_get_min_max_info((MINMAXINFO*)lp);\n            break;\n        case WM_CLOSE        :\n            ui_view.set_focus(null); // before WM_CLOSING\n            ui_app_post_message(ui.message.closing, 0, 0);\n            return 0;\n        case WM_DESTROY      :\n            PostQuitMessage(ui_app.exit_code);\n            break;\n        case WM_ACTIVATE         :\n            ui_app_wm_activate(wp);\n            break;\n        case WM_SYSCOMMAND  :\n            if (ui_app_wm_sys_command(wp, lp)) { return 0; }\n            break;\n        case WM_WINDOWPOSCHANGING:\n            ui_app_wm_window_position_changing(wp, lp);\n            break;\n        case WM_WINDOWPOSCHANGED:\n            ui_app_window_position_changed((WINDOWPOS*)lp);\n            break;\n        case WM_NCHITTEST    :\n            return ui_app_wm_nc_hit_test(wp, lp);\n        case WM_SYSKEYDOWN   :\n            return ui_app_wm_sys_key_down(wp, lp);\n        case WM_SYSCHAR      :\n            if (wp == VK_MENU) { return 0; } // swallow - no DefWindowProc()\n            break;\n        case WM_KEYDOWN      :\n            ui_app_alt_ctrl_shift(true, wp);\n            if (ui_app_wm_key_pressed(ui_app.root, wp)) { return 0; } // swallow\n            break;\n        case WM_SYSKEYUP:\n        case WM_KEYUP        :\n            ui_app_alt_ctrl_shift(false, wp);\n            ui_view.key_released(ui_app.root, wp);\n            break;\n        case WM_TIMER        :\n            ui_app_wm_timer((ui_timer_t)wp);\n            break;\n        case WM_ERASEBKGND   :\n            return true; // no DefWindowProc()\n        case WM_INPUTLANGCHANGE:\n            ui_app_wm_input_language_change(wp);\n            break;\n        case WM_CHAR         :\n            ui_app_wm_char(ui_app.root, (const uint16_t*)&wp);\n            break;\n        case WM_PRINTCLIENT  :\n            ui_app_paint_on_canvas((HDC)wp);\n            break;\n        case WM_SETFOCUS     :\n            ui_app_wm_set_focus();\n            break;\n        case WM_KILLFOCUS    :\n            ui_app_wm_kill_focus();\n            break;\n        case WM_NCCALCSIZE:\n            return ui_app_wm_nc_calculate_size(wp, lp);\n        case WM_PAINT        :\n            ui_app_wm_paint();\n            break;\n        case WM_CONTEXTMENU  :\n            (void)ui_view.context_menu(ui_app.root);\n            break;\n        case WM_THEMECHANGED :\n            ui_theme.refresh();\n            break;\n        case WM_SETTINGCHANGE:\n            ui_app_setting_change((uintptr_t)wp, (uintptr_t)lp);\n            break;\n        case WM_GETDPISCALEDSIZE: // sent before WM_DPICHANGED\n            return ui_app_wm_get_dpi_scaled_size(wp);\n        case WM_DPICHANGED  :\n            ui_app_wm_dpi_changed();\n            break;\n        case WM_NCLBUTTONDOWN   : case WM_NCRBUTTONDOWN  : case WM_NCMBUTTONDOWN  :\n        case WM_NCLBUTTONUP     : case WM_NCRBUTTONUP    : case WM_NCMBUTTONUP    :\n        case WM_NCLBUTTONDBLCLK : case WM_NCRBUTTONDBLCLK: case WM_NCMBUTTONDBLCLK:\n        case WM_NCMOUSEMOVE     :\n            ui_app_nc_mouse_buttons(m, wp, lp);\n            break;\n        case WM_LBUTTONDOWN     : case WM_RBUTTONDOWN  : case WM_MBUTTONDOWN  :\n        case WM_LBUTTONUP       : case WM_RBUTTONUP    : case WM_MBUTTONUP    :\n        case WM_LBUTTONDBLCLK   : case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK:\n//          if (m == WM_LBUTTONDOWN)   { rt_println(\"WM_LBUTTONDOWN\"); }\n//          if (m == WM_LBUTTONUP)     { rt_println(\"WM_LBUTTONUP\"); }\n//          if (m == WM_LBUTTONDBLCLK) { rt_println(\"WM_LBUTTONDBLCLK\"); }\n            if (ui_app_wm_mouse(m, wp, lp)) { return 0; }\n            break;\n        case WM_MOUSEHOVER      :\n        case WM_MOUSEMOVE       :\n            if (ui_app_wm_mouse(m, wp, lp)) { return 0; }\n            break;\n        case WM_MOUSEWHEEL   :\n            ui_app_wm_mouse_wheel(true, wp);\n            break;\n        case WM_MOUSEHWHEEL  :\n            ui_app_wm_mouse_wheel(false, wp);\n            break;\n        // debugging:\n        #ifdef UI_APP_DEBUGING_ALT_KEYBOARD_SHORTCUTS\n        case WM_PARENTNOTIFY  : rt_println(\"WM_PARENTNOTIFY\");     break;\n        case WM_ENTERMENULOOP : rt_println(\"WM_ENTERMENULOOP\");    return 0;\n        case WM_EXITMENULOOP  : rt_println(\"WM_EXITMENULOOP\");     return 0;\n        case WM_INITMENU      : rt_println(\"WM_INITMENU\");         return 0;\n        case WM_MENUCHAR      : rt_println(\"WM_MENUCHAR\");         return MNC_CLOSE << 16;\n        case WM_CAPTURECHANGED: rt_println(\"WM_CAPTURECHANGED\");   break;\n        case WM_MENUSELECT    : rt_println(\"WM_MENUSELECT\");       return 0;\n        #else\n        // ***Important***: prevents annoying beeps on Alt+Shortcut\n        case WM_MENUCHAR      : return MNC_CLOSE << 16;\n        // TODO: may be beeps are good if no UI controls reacted\n        #endif\n        // TODO: investigate WM_SETCURSOR in regards to wait cursor\n        case WM_SETCURSOR    :\n            if (LOWORD(lp) == HTCLIENT) { // see WM_NCHITTEST\n                SetCursor((HCURSOR)ui_app.cursor);\n                return true; // must NOT call DefWindowProc()\n            }\n            break;\n#ifdef UI_APP_USE_WM_IME\n        case WM_IME_CHAR:\n            rt_println(\"WM_IME_CHAR: 0x%04X\", wp);\n            break;\n        case WM_IME_NOTIFY:\n            rt_println(\"WM_IME_NOTIFY\");\n            break;\n        case WM_IME_REQUEST:\n            rt_println(\"WM_IME_REQUEST\");\n            break;\n        case WM_IME_STARTCOMPOSITION:\n            rt_println(\"WM_IME_STARTCOMPOSITION\");\n            break;\n        case WM_IME_ENDCOMPOSITION:\n            rt_println(\"WM_IME_ENDCOMPOSITION\");\n            break;\n        case WM_IME_COMPOSITION:\n            rt_println(\"WM_IME_COMPOSITION\");\n            ui_app_ime_composition(lp);\n            break;\n#endif  // UI_APP_USE_WM_IME\n        // TODO:\n        case WM_UNICHAR       : // only UTF-32 via PostMessage?\n            rt_println(\"???\");\n            // see: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input\n            // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-tounicode\n            break;\n        default:\n            break;\n    }\n    return DefWindowProcW(ui_app_window(), (UINT)m, (WPARAM)wp, lp);\n}\n\nstatic long ui_app_get_window_long(int32_t index) {\n    rt_core.set_err(0);\n    long v = GetWindowLongA(ui_app_window(), index);\n    rt_fatal_if_error(rt_core.err());\n    return v;\n}\n\nstatic long ui_app_set_window_long(int32_t index, long value) {\n    rt_core.set_err(0);\n    long r = SetWindowLongA(ui_app_window(), index, value); // r previous value\n    rt_fatal_if_error(rt_core.err());\n    return r;\n}\n\nstatic void ui_app_modify_window_style(uint32_t include, uint32_t exclude) {\n    long s = ui_app_get_window_long(GWL_STYLE);\n    s &= ~exclude;\n    s |=  include;\n    ui_app_set_window_long(GWL_STYLE, s);\n}\n\nstatic DWORD ui_app_window_style(void) {\n    return ui_app.no_decor ? WS_POPUPWINDOW|\n                             WS_THICKFRAME|\n                             WS_MINIMIZEBOX\n                           : WS_OVERLAPPEDWINDOW;\n}\n\nstatic errno_t ui_app_set_layered_window(ui_color_t color, fp32_t alpha) {\n    uint8_t  a = 0; // alpha 0..255\n    uint32_t c = 0; // R8G8B8\n    DWORD mask = 0;\n    if (0 <= alpha && alpha <= 1.0f) {\n        mask |= LWA_ALPHA;\n        a = (uint8_t)(alpha * 255 + 0.5f);\n    }\n    if (color != ui_color_undefined) {\n        mask |= LWA_COLORKEY;\n        rt_assert(ui_color_is_8bit(color));\n        c = ui_gdi.color_rgb(color);\n    }\n    return rt_b2e(SetLayeredWindowAttributes(ui_app_window(), c, a, mask));\n}\n\nstatic void ui_app_set_dwm_attribute(uint32_t mode, void* a, DWORD bytes) {\n    rt_fatal_if_error(DwmSetWindowAttribute(ui_app_window(), mode, a, bytes));\n}\n\nstatic void ui_app_init_dwm(void) {\n    if (IsWindowsVersionOrGreater(10, 0, 22000)) {\n        // do not call on Win10 - will fail\n        DWM_WINDOW_CORNER_PREFERENCE c = DWMWCP_ROUND;\n        ui_app_set_dwm_attribute(DWMWA_WINDOW_CORNER_PREFERENCE, &c, sizeof(c));\n        COLORREF cc = (COLORREF)ui_gdi.color_rgb(ui_color_rgb(45, 45, 48));\n        ui_app_set_dwm_attribute(DWMWA_CAPTION_COLOR, &cc, sizeof(cc));\n    }\n    BOOL e = true; // must be 32-bit BOOL because of sizeof()\n    ui_app_set_dwm_attribute(DWMWA_USE_IMMERSIVE_DARK_MODE, &e, sizeof(e));\n    // kudos for double negatives - so easy to make mistakes:\n    ui_app_set_dwm_attribute(DWMWA_TRANSITIONS_FORCEDISABLED, &e, sizeof(e));\n    enum DWMNCRENDERINGPOLICY rp = DWMNCRP_USEWINDOWSTYLE;\n    ui_app_set_dwm_attribute(DWMWA_NCRENDERING_POLICY, &rp, sizeof(rp));\n    if (ui_app.no_decor) {\n        ui_app_set_dwm_attribute(DWMWA_ALLOW_NCPAINT, &e, sizeof(e));\n        MARGINS margins = { 0, 0, 0, 0 };\n        rt_fatal_if_error(\n            DwmExtendFrameIntoClientArea(ui_app_window(), &margins)\n        );\n    }\n}\n\nstatic void ui_app_swp(HWND top, int32_t x, int32_t y, int32_t w, int32_t h,\n        uint32_t f) {\n    rt_fatal_win32err(SetWindowPos(ui_app_window(), top, x, y, w, h, f));\n}\n\nstatic void ui_app_swp_flags(uint32_t f) {\n    rt_fatal_win32err(SetWindowPos(ui_app_window(), null, 0, 0, 0, 0, f));\n}\n\nstatic void ui_app_disable_sys_menu_item(HMENU sys_menu, uint32_t item) {\n    const uint32_t f = MF_BYCOMMAND | MF_DISABLED;\n    rt_fatal_win32err(EnableMenuItem(sys_menu, item, f));\n}\n\nstatic void ui_app_init_sys_menu(void) {\n    // tried to remove unused items from system menu which leads to\n    // AllowDarkModeForWindow() failed 0x000005B0(1456) \"A menu item was not found.\"\n    // SetPreferredAppMode() failed 0x000005B0(1456) \"A menu item was not found.\"\n    // this is why they just disabled instead.\n    HMENU sys_menu = GetSystemMenu(ui_app_window(), false);\n    rt_not_null(sys_menu);\n    if (ui_app.no_min || ui_app.no_max) {\n        int32_t exclude = WS_SIZEBOX;\n        if (ui_app.no_min) { exclude = WS_MINIMIZEBOX; }\n        if (ui_app.no_max) { exclude = WS_MAXIMIZEBOX; }\n        ui_app_modify_window_style(0, exclude);\n        if (ui_app.no_min) { ui_app_disable_sys_menu_item(sys_menu, SC_MINIMIZE); }\n        if (ui_app.no_max) { ui_app_disable_sys_menu_item(sys_menu, SC_MAXIMIZE); }\n    }\n    if (ui_app.no_size) {\n        ui_app_disable_sys_menu_item(sys_menu, SC_SIZE);\n        ui_app_modify_window_style(0, WS_SIZEBOX);\n        const uint32_t f = SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |\n                           SWP_NOACTIVATE;\n        ui_app_swp_flags(f);\n    }\n}\n\nstatic void ui_app_create_window(const ui_rect_t r) {\n    uint16_t class_name[256];\n    rt_str.utf8to16(class_name, rt_countof(class_name), ui_app.class_name, -1);\n    WNDCLASSW* wc = &ui_app_wc;\n    // CS_DBLCLKS no longer needed. Because code detects long-press\n    // it does double click too. Editor uses both for word and paragraph select.\n    wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_SAVEBITS;\n    wc->lpfnWndProc = ui_app_window_proc;\n    wc->cbClsExtra = 0;\n    wc->cbWndExtra = 256 * 1024;\n    wc->hInstance = GetModuleHandleA(null);\n    wc->hIcon = (HICON)ui_app.icon;\n    wc->hCursor = (HCURSOR)ui_app.cursor;\n    wc->hbrBackground = null;\n    wc->lpszMenuName = null;\n    wc->lpszClassName = class_name;\n    ATOM atom = RegisterClassW(wc);\n    rt_fatal_if(atom == 0);\n    uint16_t title[256];\n    rt_str.utf8to16(title, rt_countof(title), ui_app.title, -1);\n    HWND window = CreateWindowExW(WS_EX_COMPOSITED | WS_EX_LAYERED,\n        class_name, title, ui_app_window_style(),\n        r.x, r.y, r.w, r.h, null, null, wc->hInstance, null);\n    rt_not_null(ui_app.window);\n    rt_swear(window == ui_app_window());\n    ui_app.show_window(ui.visibility.hide);\n    ui_view.set_text(&ui_caption.title, \"%s\", ui_app.title);\n    ui_app.dpi.window = (int32_t)GetDpiForWindow(ui_app_window());\n    RECT wrc = ui_app_ui2rect(&r);\n    rt_fatal_win32err(GetWindowRect(ui_app_window(), &wrc));\n    ui_app.wrc = ui_app_rect2ui(&wrc);\n    ui_app_init_dwm();\n    ui_app_init_sys_menu();\n    ui_theme.refresh();\n    if (ui_app.visibility != ui.visibility.hide) {\n        AnimateWindow(ui_app_window(), 250, AW_ACTIVATE);\n        ui_app.show_window(ui_app.visibility);\n        ui_app_update_crc();\n    }\n    // even if it is hidden:\n    ui_app_post_message(ui.message.opening, 0, 0);\n//  SetWindowTheme(ui_app_window(), L\"DarkMode_Explorer\", null); ???\n}\n\nstatic void ui_app_full_screen(bool on) {\n    static long style;\n    static WINDOWPLACEMENT wp;\n    if (on != ui_app.is_full_screen) {\n        ui_app_show_task_bar(!on);\n        if (on) {\n            ui_app_modify_window_style(0, WS_OVERLAPPEDWINDOW|WS_POPUPWINDOW);\n            ui_app_modify_window_style(WS_POPUP | WS_VISIBLE, 0);\n            wp.length = sizeof(wp);\n            rt_fatal_win32err(GetWindowPlacement(ui_app_window(), &wp));\n            WINDOWPLACEMENT nwp = wp;\n            nwp.showCmd = SW_SHOWNORMAL;\n            nwp.rcNormalPosition = (RECT){ui_app.mrc.x, ui_app.mrc.y,\n                ui_app.mrc.x + ui_app.mrc.w, ui_app.mrc.y + ui_app.mrc.h};\n            rt_fatal_win32err(SetWindowPlacement(ui_app_window(), &nwp));\n        } else {\n            rt_fatal_win32err(SetWindowPlacement(ui_app_window(), &wp));\n            ui_app_set_window_long(GWL_STYLE, ui_app_window_style());\n            enum { flags = SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |\n                           SWP_NOZORDER | SWP_NOOWNERZORDER };\n            ui_app_swp_flags(flags);\n        }\n        ui_app.is_full_screen = on;\n    }\n}\n\nstatic bool ui_app_set_focus(ui_view_t* rt_unused(v)) { return false; }\n\nstatic void ui_app_request_redraw(void) {  // < 2us\n    SetEvent(ui_app_event_invalidate);\n}\n\nstatic void ui_app_draw(void) {\n    rt_println(\"avoid at all cost. bad performance, bad UX\");\n    UpdateWindow(ui_app_window());\n}\n\nstatic void ui_app_invalidate_rect(const ui_rect_t* r) {\n    RECT rc = ui_app_ui2rect(r);\n    InvalidateRect(ui_app_window(), &rc, false);\n//  rt_backtrace_here();\n}\n\nstatic int32_t ui_app_message_loop(void) {\n    MSG msg = {0};\n    while (GetMessageW(&msg, null, 0, 0)) {\n        if (msg.message == WM_KEYDOWN    || msg.message == WM_KEYUP ||\n            msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) {\n            // before TranslateMessage():\n            ui_app_decode_keyboard(msg.message, msg.wParam, msg.lParam);\n        }\n        TranslateMessage(&msg);\n        DispatchMessageW(&msg);\n    }\n    rt_work_queue.flush(&ui_app_queue);\n    rt_assert(msg.message == WM_QUIT);\n    return (int32_t)msg.wParam;\n}\n\nstatic void ui_app_dispose(void) {\n    ui_app_dispose_fonts();\n    rt_event.dispose(ui_app_event_invalidate);\n    ui_app_event_invalidate = null;\n}\n\nstatic void ui_app_cursor_set(ui_cursor_t c) {\n    // https://docs.microsoft.com/en-us/windows/win32/menurc/using-cursors\n    ui_app.cursor = c;\n    SetClassLongPtr(ui_app_window(), GCLP_HCURSOR, (LONG_PTR)c);\n    POINT pt = {0};\n    if (GetCursorPos(&pt)) { SetCursorPos(pt.x + 1, pt.y); SetCursorPos(pt.x, pt.y); }\n}\n\nstatic void ui_app_close_window(void) {\n    // TODO: fix me. Band aid - start up with maximized no_decor window is broken\n    if (ui_app.is_maximized()) { ui_app.show_window(ui.visibility.restore); }\n    ui_app_post_message(WM_CLOSE, 0, 0);\n}\n\nstatic void ui_app_quit(int32_t exit_code) {\n    ui_app.exit_code = exit_code;\n    if (ui_app.can_close != null) {\n        (void)ui_app.can_close(); // and deliberately ignore result\n    }\n    ui_app.can_close = null; // will not be called again\n    ui_app.close(); // close and destroy app only window\n}\n\nstatic void ui_app_show_hint_or_toast(ui_view_t* v, int32_t x, int32_t y,\n        fp64_t timeout) {\n    if (v != null) {\n        ui_app.animating.x = x;\n        ui_app.animating.y = y;\n        ui_app.animating.focused = ui_app.focus;\n        if (v->type == ui_view_mbx) {\n            ((ui_mbx_t*)v)->option = -1;\n            if (v->focusable) {\n                 ui_view.set_focus(v);\n            }\n        }\n        // allow unparented ui for toast and hint\n        ui_view_call_init(v);\n        const int32_t steps = x < 0 && y < 0 ? ui_app_animation_steps : 1;\n        ui_app_animate_start(ui_app_toast_dim, steps);\n        ui_app.animating.view = v;\n        v->parent = ui_app.root;\n        if (v->focusable) { ui_view.set_focus(v); }\n        ui_app.animating.time = timeout > 0 ? ui_app.now + timeout : 0;\n    } else {\n        ui_app_toast_cancel();\n    }\n}\n\nstatic void ui_app_show_toast(ui_view_t* view, fp64_t timeout) {\n    ui_app_show_hint_or_toast(view, -1, -1, timeout);\n}\n\nstatic void ui_app_show_hint(ui_view_t* view, int32_t x, int32_t y,\n        fp64_t timeout) {\n    if (view != null) {\n        ui_app_show_hint_or_toast(view, x, y, timeout);\n    } else if (ui_app.animating.view != null && ui_app.animating.x >= 0 &&\n               ui_app.animating.y >= 0) {\n        ui_app_toast_cancel(); // only cancel hints not toasts\n    }\n}\n\nstatic void ui_app_formatted_toast_va(fp64_t timeout, const char* format, va_list va) {\n    ui_app_show_toast(null, 0);\n    static ui_label_t label = ui_label(0.0, \"\");\n    ui_label_init_va(&label, 0.0, format, va);\n    ui_app_show_toast(&label, timeout);\n}\n\nstatic void ui_app_formatted_toast(fp64_t timeout, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_app_formatted_toast_va(timeout, format, va);\n    va_end(va);\n}\n\nstatic int32_t ui_app_caret_w;\nstatic int32_t ui_app_caret_h;\nstatic int32_t ui_app_caret_x = -1;\nstatic int32_t ui_app_caret_y = -1;\nstatic bool    ui_app_caret_shown;\n\nstatic void ui_app_create_caret(int32_t w, int32_t h) {\n    ui_app_caret_w = w;\n    ui_app_caret_h = h;\n    rt_fatal_win32err(CreateCaret(ui_app_window(), null, w, h));\n    rt_assert(GetSystemMetrics(SM_CARETBLINKINGENABLED));\n}\n\nstatic void ui_app_invalidate_caret(void) {\n    if (ui_app_caret_w >  0 && ui_app_caret_h >  0 &&\n        ui_app_caret_x >= 0 && ui_app_caret_y >= 0 &&\n        ui_app_caret_shown) {\n        RECT rc = { ui_app_caret_x, ui_app_caret_y,\n                    ui_app_caret_x + ui_app_caret_w,\n                    ui_app_caret_y + ui_app_caret_h };\n        rt_fatal_win32err(InvalidateRect(ui_app_window(), &rc, false));\n    }\n}\n\nstatic void ui_app_show_caret(void) {\n    rt_assert(!ui_app_caret_shown);\n    rt_fatal_win32err(ShowCaret(ui_app_window()));\n    ui_app_caret_shown = true;\n    ui_app_invalidate_caret();\n}\n\nstatic void ui_app_move_caret(int32_t x, int32_t y) {\n    ui_app_invalidate_caret(); // where is was\n    ui_app_caret_x = x;\n    ui_app_caret_y = y;\n    rt_fatal_win32err(SetCaretPos(x, y));\n    ui_app_invalidate_caret(); // where it is now\n}\n\nstatic void ui_app_hide_caret(void) {\n    rt_assert(ui_app_caret_shown);\n    rt_fatal_win32err(HideCaret(ui_app_window()));\n    ui_app_invalidate_caret();\n    ui_app_caret_shown = false;\n}\n\nstatic void ui_app_destroy_caret(void) {\n    ui_app_caret_w = 0;\n    ui_app_caret_h = 0;\n    rt_fatal_win32err(DestroyCaret());\n}\n\nstatic void ui_app_beep(int32_t kind) {\n    static int32_t beep_id[] = { MB_OK, MB_ICONINFORMATION, MB_ICONQUESTION,\n                          MB_ICONWARNING, MB_ICONERROR};\n    rt_swear(0 <= kind && kind < rt_countof(beep_id));\n    rt_fatal_win32err(MessageBeep(beep_id[kind]));\n}\n\nstatic void ui_app_enable_sys_command_close(void) {\n    EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false),\n        SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);\n}\n\nstatic void ui_app_console_disable_close(void) {\n    EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false),\n        SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);\n    (void)freopen(\"CONOUT$\", \"w\", stdout);\n    (void)freopen(\"CONOUT$\", \"w\", stderr);\n    atexit(ui_app_enable_sys_command_close);\n}\n\nstatic int ui_app_console_attach(void) {\n    int r = AttachConsole(ATTACH_PARENT_PROCESS) ? 0 : rt_core.err();\n    if (r == 0) {\n        ui_app_console_disable_close();\n        rt_thread.sleep_for(0.1); // give cmd.exe a chance to print prompt again\n        printf(\"\\n\");\n    }\n    return r;\n}\n\nstatic bool ui_app_is_stdout_redirected(void) {\n    // https://stackoverflow.com/questions/30126490/how-to-check-if-stdout-is-redirected-to-a-file-or-to-a-console\n    HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);\n    DWORD type = out == null ? FILE_TYPE_UNKNOWN : GetFileType(out);\n    type &= ~(DWORD)FILE_TYPE_REMOTE;\n    // FILE_TYPE_DISK or FILE_TYPE_CHAR or FILE_TYPE_PIPE\n    return type != FILE_TYPE_UNKNOWN;\n}\n\nstatic bool ui_app_is_console_visible(void) {\n    HWND cw = GetConsoleWindow();\n    return cw != null && IsWindowVisible(cw);\n}\n\nstatic int ui_app_set_console_size(int16_t w, int16_t h) {\n    // width/height in characters\n    HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n    CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n    int r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    if (r != 0) {\n        rt_println(\"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    } else {\n        // tricky because correct order of the calls\n        // SetConsoleWindowInfo() SetConsoleScreenBufferSize() depends on\n        // current Window Size (in pixels) ConsoleWindowSize(in characters)\n        // and SetConsoleScreenBufferSize().\n        // After a lot of experimentation and reading docs most sensible option\n        // is to try both calls in two different orders.\n        COORD c = {w, h};\n        SMALL_RECT const min_win = { 0, 0, c.X - 1, c.Y - 1 };\n        c.Y = 9001; // maximum buffer number of rows at the moment of implementation\n        int r0 = SetConsoleWindowInfo(console, true, &min_win) ? 0 : rt_core.err();\n//      if (r0 != 0) { rt_println(\"SetConsoleWindowInfo() %s\", rt_strerr(r0)); }\n        int r1 = SetConsoleScreenBufferSize(console, c) ? 0 : rt_core.err();\n//      if (r1 != 0) { rt_println(\"SetConsoleScreenBufferSize() %s\", rt_strerr(r1)); }\n        if (r0 != 0 || r1 != 0) { // try in reverse order (which expected to work):\n            r0 = SetConsoleScreenBufferSize(console, c) ? 0 : rt_core.err();\n            if (r0 != 0) { rt_println(\"SetConsoleScreenBufferSize() %s\", rt_strerr(r0)); }\n            r1 = SetConsoleWindowInfo(console, true, &min_win) ? 0 : rt_core.err();\n            if (r1 != 0) { rt_println(\"SetConsoleWindowInfo() %s\", rt_strerr(r1)); }\n\t    }\n        r = r0 == 0 ? r1 : r0; // first of two errors\n    }\n    return r;\n}\n\nstatic void ui_app_console_largest(void) {\n    HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);\n    // User have to manual uncheck \"[x] Let system position window\" in console\n    // Properties -> Layout -> Window Position because I did not find the way\n    // to programmatically unchecked it.\n    // commented code below does not work.\n    // see: https://www.os2museum.com/wp/disabling-quick-edit-mode/\n    // and: https://learn.microsoft.com/en-us/windows/console/setconsolemode\n    /* DOES NOT WORK:\n    DWORD mode = 0;\n    r = GetConsoleMode(console, &mode) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleMode() %s\", rt_strerr(r));\n    mode &= ~ENABLE_AUTO_POSITION;\n    r = SetConsoleMode(console, &mode) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"SetConsoleMode() %s\", rt_strerr(r));\n    */\n    CONSOLE_SCREEN_BUFFER_INFOEX info = { sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };\n    int r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    COORD c = GetLargestConsoleWindowSize(console);\n    if (c.X > 80) { c.X &= ~0x7; }\n    if (c.Y > 24) { c.Y &= ~0x3; }\n    if (c.X > 80) { c.X -= 8; }\n    if (c.Y > 24) { c.Y -= 4; }\n    ui_app_set_console_size(c.X, c.Y);\n    r = GetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"GetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    info.dwSize.Y = 9999; // maximum value at the moment of implementation\n    r = SetConsoleScreenBufferInfoEx(console, &info) ? 0 : rt_core.err();\n    rt_fatal_if_error(r, \"SetConsoleScreenBufferInfoEx() %s\", rt_strerr(r));\n    ui_app_save_console_pos();\n}\n\nstatic void ui_app_make_topmost(void) {\n    //  Places the window above all non-topmost windows.\n    // The window maintains its topmost position even when it is deactivated.\n    enum { swp = SWP_SHOWWINDOW | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOSIZE };\n    ui_app_swp(HWND_TOPMOST, 0, 0, 0, 0, swp);\n}\n\nstatic void ui_app_activate(void) {\n    rt_core.set_err(0);\n    HWND previous = SetActiveWindow(ui_app_window());\n    if (previous == null) { rt_fatal_if_error(rt_core.err()); }\n}\n\nstatic void ui_app_bring_to_foreground(void) {\n    // SetForegroundWindow() does not activate window:\n    rt_fatal_win32err(SetForegroundWindow(ui_app_window()));\n}\n\nstatic void ui_app_bring_to_front(void) {\n    ui_app.bring_to_foreground();\n    ui_app.make_topmost();\n    ui_app.bring_to_foreground();\n    // because bring_to_foreground() does not activate\n    ui_app.activate();\n    ui_app.request_focus();\n}\n\nstatic void ui_app_set_title(const char* title) {\n    ui_view.set_text(&ui_caption.title, \"%s\", title);\n    rt_fatal_win32err(SetWindowTextA(ui_app_window(), rt_nls.str(title)));\n}\n\nstatic void ui_app_capture_mouse(bool on) {\n    static int32_t mouse_capture;\n    if (on) {\n        rt_swear(mouse_capture == 0);\n        mouse_capture++;\n        SetCapture(ui_app_window());\n    } else {\n        rt_swear(mouse_capture == 1);\n        mouse_capture--;\n        ReleaseCapture();\n    }\n}\n\nstatic void ui_app_move_and_resize(const ui_rect_t* rc) {\n    enum { swp = SWP_NOZORDER | SWP_NOACTIVATE };\n    ui_app_swp(null, rc->x, rc->y, rc->w, rc->h, swp);\n}\n\nstatic void ui_app_set_console_title(HWND cw) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    static char text[256];\n    text[0] = 0;\n    GetWindowTextA((HWND)ui_app.window, text, rt_countof(text));\n    text[rt_countof(text) - 1] = 0;\n    char title[256];\n    rt_str_printf(title, \"%s - Console\", text);\n    rt_fatal_win32err(SetWindowTextA(cw, title));\n}\n\nstatic void ui_app_restore_console(int32_t *visibility) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null) {\n        RECT wr = {0};\n        GetWindowRect(cw, &wr);\n        ui_rect_t rc = ui_app_rect2ui(&wr);\n        ui_app_load_console_pos(&rc, visibility);\n        if (rc.w > 0 && rc.h > 0) {\n//          rt_println(\"%d,%d %dx%d px\", rc.x, rc.y, rc.w, rc.h);\n            CONSOLE_SCREEN_BUFFER_INFOEX info = {\n                sizeof(CONSOLE_SCREEN_BUFFER_INFOEX)\n            };\n            int32_t r = rt_config.load(ui_app.class_name,\n                \"console_screen_buffer_infoex\", &info, (int32_t)sizeof(info));\n            if (r == sizeof(info)) { // 24x80\n                SMALL_RECT sr = info.srWindow;\n                int16_t w = (int16_t)rt_max(sr.Right - sr.Left + 1, 80);\n                int16_t h = (int16_t)rt_max(sr.Bottom - sr.Top + 1, 24);\n//              rt_println(\"info: %dx%d\", info.dwSize.X, info.dwSize.Y);\n//              rt_println(\"%d,%d %dx%d\", sr.Left, sr.Top, w, h);\n                if (w > 0 && h > 0) { ui_app_set_console_size(w, h); }\n    \t    }\n            // do not resize console window just restore it's position\n            enum { flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE };\n            rt_fatal_win32err(SetWindowPos(cw, null,\n                    rc.x, rc.y, rc.w, rc.h, flags));\n        } else {\n            ui_app_console_largest();\n        }\n    }\n}\n\nstatic void ui_app_console_show(bool b) {\n    HWND cw = GetConsoleWindow();\n    if (cw != null && b != ui_app.is_console_visible()) {\n        if (ui_app.is_console_visible()) { ui_app_save_console_pos(); }\n        if (b) {\n            int32_t ignored_visibility = 0;\n            ui_app_restore_console(&ignored_visibility);\n            ui_app_set_console_title(cw);\n        }\n        // If the window was previously visible, the return value is nonzero.\n        // If the window was previously hidden, the return value is zero.\n        bool unused_was_visible = ShowWindow(cw, b ? SW_SHOWNOACTIVATE : SW_HIDE);\n        (void)unused_was_visible;\n        if (b) { InvalidateRect(cw, null, true); SetActiveWindow(cw); }\n        ui_app_save_console_pos(); // again after visibility changed\n    }\n}\n\nstatic int ui_app_console_create(void) {\n    int r = AllocConsole() ? 0 : rt_core.err();\n    if (r == 0) {\n        ui_app_console_disable_close();\n        int32_t visibility = 0;\n        ui_app_restore_console(&visibility);\n        ui_app.console_show(visibility != 0);\n    }\n    return r;\n}\n\nstatic fp32_t ui_app_px2in(int32_t pixels) {\n    rt_assert(ui_app.dpi.monitor_max > 0);\n//  rt_println(\"ui_app.dpi.monitor_raw: %d\", ui_app.dpi.monitor_max);\n    return ui_app.dpi.monitor_max > 0 ?\n           (fp32_t)pixels / (fp32_t)ui_app.dpi.monitor_max : 0;\n}\n\nstatic int32_t ui_app_in2px(fp32_t inches) {\n    rt_assert(ui_app.dpi.monitor_max > 0);\n//  rt_println(\"ui_app.dpi.monitor_raw: %d\", ui_app.dpi.monitor_max);\n    return (int32_t)(inches * (fp64_t)ui_app.dpi.monitor_max + 0.5);\n}\n\nstatic void ui_app_request_layout(void) {\n    ui_app_layout_dirty = true;\n    ui_app.request_redraw();\n}\n\nstatic void ui_app_show_window(int32_t show) {\n    rt_assert(ui.visibility.hide <= show &&\n           show <= ui.visibility.force_min);\n    // ShowWindow() does not have documented error reporting\n    bool was_visible = ShowWindow(ui_app_window(), show);\n    (void)was_visible;\n    const bool hiding =\n        show == ui.visibility.hide ||\n        show == ui.visibility.minimize ||\n        show == ui.visibility.show_na ||\n        show == ui.visibility.min_na;\n    if (!hiding) {\n        ui_app.bring_to_foreground(); // this does not make it ActiveWindow\n        enum { flags = SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOSIZE |\n                       SWP_NOREPOSITION | SWP_NOMOVE };\n        ui_app_swp_flags(flags);\n        ui_app.request_focus();\n    } else if (show == ui.visibility.hide ||\n               show == ui.visibility.minimize ||\n               show == ui.visibility.min_na) {\n        ui_app_toast_cancel();\n    }\n}\n\nstatic const char* ui_app_open_file(const char* folder,\n        const char* pairs[], int32_t n) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_assert(pairs == null && n == 0 || n >= 2 && n % 2 == 0);\n    static uint16_t memory[4 * 1024];\n    uint16_t* filter = memory;\n    if (pairs == null || n == 0) {\n        filter = L\"All Files\\0*\\0\\0\";\n    } else {\n        int32_t left = rt_countof(memory) - 2;\n        uint16_t* s = memory;\n        for (int32_t i = 0; i < n; i+= 2) {\n            uint16_t* s0 = s;\n            rt_str.utf8to16(s0, left, pairs[i + 0], -1);\n            int32_t n0 = (int32_t)rt_str.len16(s0);\n            rt_assert(n0 > 0);\n            s += n0 + 1;\n            left -= n0 + 1;\n            uint16_t* s1 = s;\n            rt_str.utf8to16(s1, left, pairs[i + 1], -1);\n            int32_t n1 = (int32_t)rt_str.len16(s1);\n            rt_assert(n1 > 0);\n            s[n1] = 0;\n            s += n1 + 1;\n            left -= n1 + 1;\n        }\n        *s++ = 0;\n    }\n    static uint16_t dir[rt_files_max_path];\n    dir[0] = 0;\n    rt_str.utf8to16(dir, rt_countof(dir), folder, -1);\n    static uint16_t path[rt_files_max_path];\n    path[0] = 0;\n    OPENFILENAMEW ofn = { sizeof(ofn) };\n    ofn.hwndOwner = (HWND)ui_app.window;\n    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;\n    ofn.lpstrFilter = filter;\n    ofn.lpstrInitialDir = dir;\n    ofn.lpstrFile = path;\n    ofn.nMaxFile = sizeof(path);\n    static rt_file_name_t fn;\n    fn.s[0] = 0;\n    if (GetOpenFileNameW(&ofn) && path[0] != 0) {\n        rt_str.utf16to8(fn.s, rt_countof(fn.s), path, -1);\n    } else {\n        fn.s[0] = 0;\n    }\n    return fn.s;\n}\n\n// TODO: use clipboard instead?\n\nstatic errno_t ui_app_clipboard_put_image(ui_bitmap_t* im) {\n    HDC canvas = GetDC(null);\n    rt_not_null(canvas);\n    HDC src = CreateCompatibleDC(canvas); rt_not_null(src);\n    HDC dst = CreateCompatibleDC(canvas); rt_not_null(dst);\n    // CreateCompatibleBitmap(dst) will create monochrome bitmap!\n    // CreateCompatibleBitmap(canvas) will create display compatible\n    HBITMAP texture = CreateCompatibleBitmap(canvas, im->w, im->h);\n    rt_not_null(texture);\n    HBITMAP s = SelectBitmap(src, im->texture); rt_not_null(s);\n    HBITMAP d = SelectBitmap(dst, texture);     rt_not_null(d);\n    POINT pt = { 0 };\n    rt_fatal_win32err(SetBrushOrgEx(dst, 0, 0, &pt));\n    rt_fatal_win32err(StretchBlt(dst, 0, 0, im->w, im->h, src, 0, 0,\n        im->w, im->h, SRCCOPY));\n    errno_t r = rt_b2e(OpenClipboard(GetDesktopWindow()));\n    if (r != 0) { rt_println(\"OpenClipboard() failed %s\", rt_strerr(r)); }\n    if (r == 0) {\n        r = rt_b2e(EmptyClipboard());\n        if (r != 0) { rt_println(\"EmptyClipboard() failed %s\", rt_strerr(r)); }\n    }\n    if (r == 0) {\n        r = rt_b2e(SetClipboardData(CF_BITMAP, texture));\n        if (r != 0) {\n            rt_println(\"SetClipboardData() failed %s\", rt_strerr(r));\n        }\n    }\n    if (r == 0) {\n        r = rt_b2e(CloseClipboard());\n        if (r != 0) {\n            rt_println(\"CloseClipboard() failed %s\", rt_strerr(r));\n        }\n    }\n    rt_not_null(SelectBitmap(dst, d));\n    rt_not_null(SelectBitmap(src, s));\n    rt_fatal_win32err(DeleteBitmap(texture));\n    rt_fatal_win32err(DeleteDC(dst));\n    rt_fatal_win32err(DeleteDC(src));\n    rt_fatal_win32err(ReleaseDC(null, canvas));\n    return r;\n}\n\nstatic ui_view_t ui_app_view = ui_view(list);\nstatic ui_view_t ui_app_content = ui_view(stack);\n\nstatic bool ui_app_is_active(void) { return GetActiveWindow() == ui_app_window(); }\n\nstatic bool ui_app_is_minimized(void) { return IsIconic(ui_app_window()); }\n\nstatic bool ui_app_is_maximized(void) { return IsZoomed(ui_app_window()); }\n\nstatic bool ui_app_focused(void) { return GetFocus() == ui_app_window(); }\n\nstatic void window_request_focus(void* w) {\n    // https://stackoverflow.com/questions/62649124/pywin32-setfocus-resulting-in-access-is-denied-error\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput\n    rt_assert(rt_thread.id() == ui_app.tid, \"cannot be called from background thread\");\n    rt_core.set_err(0);\n    HWND previous = SetFocus((HWND)w); // previously focused window\n    if (previous == null) { rt_fatal_if_error(rt_core.err()); }\n}\n\nstatic void ui_app_request_focus(void) {\n    window_request_focus(ui_app.window);\n}\n\nstatic void ui_app_init(void) {\n    ui_app_event_quit           = rt_event.create_manual();\n    ui_app_event_invalidate     = rt_event.create();\n    ui_app.request_redraw       = ui_app_request_redraw;\n    ui_app.post                 = ui_app_post;\n    ui_app.draw                 = ui_app_draw;\n    ui_app.px2in                = ui_app_px2in;\n    ui_app.in2px                = ui_app_in2px;\n    ui_app.set_layered_window   = ui_app_set_layered_window;\n    ui_app.is_active            = ui_app_is_active;\n    ui_app.is_minimized         = ui_app_is_minimized;\n    ui_app.is_maximized         = ui_app_is_maximized;\n    ui_app.focused              = ui_app_focused;\n    ui_app.request_focus        = ui_app_request_focus;\n    ui_app.activate             = ui_app_activate;\n    ui_app.set_title            = ui_app_set_title;\n    ui_app.capture_mouse        = ui_app_capture_mouse;\n    ui_app.move_and_resize      = ui_app_move_and_resize;\n    ui_app.bring_to_foreground  = ui_app_bring_to_foreground;\n    ui_app.make_topmost         = ui_app_make_topmost;\n    ui_app.bring_to_front       = ui_app_bring_to_front;\n    ui_app.request_layout       = ui_app_request_layout;\n    ui_app.invalidate           = ui_app_invalidate_rect;\n    ui_app.full_screen          = ui_app_full_screen;\n    ui_app.set_cursor           = ui_app_cursor_set;\n    ui_app.close                = ui_app_close_window;\n    ui_app.quit                 = ui_app_quit;\n    ui_app.set_timer            = ui_app_timer_set;\n    ui_app.kill_timer           = ui_app_timer_kill;\n    ui_app.show_window          = ui_app_show_window;\n    ui_app.show_toast           = ui_app_show_toast;\n    ui_app.show_hint            = ui_app_show_hint;\n    ui_app.toast_va             = ui_app_formatted_toast_va;\n    ui_app.toast                = ui_app_formatted_toast;\n    ui_app.create_caret         = ui_app_create_caret;\n    ui_app.show_caret           = ui_app_show_caret;\n    ui_app.move_caret           = ui_app_move_caret;\n    ui_app.hide_caret           = ui_app_hide_caret;\n    ui_app.destroy_caret        = ui_app_destroy_caret;\n    ui_app.beep                 = ui_app_beep;\n    ui_app.data_save            = ui_app_data_save;\n    ui_app.data_size            = ui_app_data_size;\n    ui_app.data_load            = ui_app_data_load;\n    ui_app.open_file            = ui_app_open_file;\n    ui_app.is_stdout_redirected = ui_app_is_stdout_redirected;\n    ui_app.is_console_visible   = ui_app_is_console_visible;\n    ui_app.console_attach       = ui_app_console_attach;\n    ui_app.console_create       = ui_app_console_create;\n    ui_app.console_show         = ui_app_console_show;\n    ui_app.root    = &ui_app_view;\n    ui_app.content = &ui_app_content;\n    ui_app.caption = &ui_caption.view;\n    ui_app.root->hit_test = ui_app_root_hit_test;\n    ui_view.add(ui_app.root, ui_app.caption, ui_app.content, null);\n    ui_view_call_init(ui_app.root); // to get done with container_init()\n    rt_assert(ui_app.content->type == ui_view_stack);\n    rt_assert(ui_app.content->background == ui_colors.transparent);\n    ui_app.root->color_id = ui_color_id_window_text;\n    ui_app.root->background_id = ui_color_id_window;\n    ui_app.root->insets  = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.root->padding = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.root->paint = ui_app_view_paint;\n    ui_app.root->max_w = ui.infinity;\n    ui_app.root->max_h = ui.infinity;\n    ui_app.content->insets  = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.content->padding = (ui_margins_t){ 0, 0, 0, 0 };\n    ui_app.content->max_w = ui.infinity;\n    ui_app.content->max_h = ui.infinity;\n    ui_app.caption->state.hidden = !ui_app.no_decor;\n    // for ui_view_debug_paint:\n    ui_view.set_text(ui_app.root, \"ui_app.root\");\n    ui_view.set_text(ui_app.content, \"ui_app.content\");\n    if (ui_app.init != null) { ui_app.init(); }\n}\n\nstatic void ui_app_set_dpi_awareness(void) {\n    // Mutually exclusive:\n    // BOOL SetProcessDpiAwarenessContext()\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext\n    // and\n    // HRESULT SetProcessDpiAwareness()\n    // https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness\n    // Plus DPI awareness can be set by APP .exe shell properties, registry\n    // or Windows policy. See:\n    // https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/\n    DPI_AWARENESS_CONTEXT dpi_awareness_context_1 =\n        GetThreadDpiAwarenessContext();\n    // https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/\n    errno_t error = rt_b2e(SetProcessDpiAwarenessContext(\n            DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2));\n    if (error == ERROR_ACCESS_DENIED) {\n        rt_println(\"Warning: SetProcessDpiAwarenessContext(): ERROR_ACCESS_DENIED\");\n        // dpi awareness already set, manifest, registry, windows policy\n        // Try via Shell:\n        HRESULT hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);\n        if (hr == E_ACCESSDENIED) {\n            rt_println(\"Warning: SetProcessDpiAwareness(): E_ACCESSDENIED\");\n        }\n    }\n    DPI_AWARENESS_CONTEXT dpi_awareness_context_2 =\n        GetThreadDpiAwarenessContext();\n    rt_swear(dpi_awareness_context_1 != dpi_awareness_context_2);\n}\n\nstatic void ui_app_init_windows(void) {\n    ui_app_set_dpi_awareness();\n    InitCommonControls(); // otherwise GetOpenFileName does not work\n    ui_app.dpi.process = (int32_t)GetSystemDpiForProcess(GetCurrentProcess());\n    ui_app.dpi.system = (int32_t)GetDpiForSystem(); // default was 96DPI\n    // monitor dpi will be reinitialized in load_window_pos\n    ui_app.dpi.monitor_effective = ui_app.dpi.system;\n    ui_app.dpi.monitor_angular = ui_app.dpi.system;\n    ui_app.dpi.monitor_raw = ui_app.dpi.system;\n    ui_app.dpi.monitor_max = ui_app.dpi.system;\n//  rt_println(\"ui_app.dpi.monitor_max := %d\", ui_app.dpi.system);\n    static const RECT nowhere = {0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF};\n    ui_rect_t r = ui_app_rect2ui(&nowhere);\n    ui_app_update_mi(&r, MONITOR_DEFAULTTOPRIMARY);\n    ui_app.dpi.window = ui_app.dpi.monitor_effective;\n}\n\nstatic ui_rect_t ui_app_window_initial_rectangle(void) {\n    const ui_window_sizing_t* ws = &ui_app.window_sizing;\n    // it is not practical and thus not implemented handling\n    // == (0, 0) and != (0, 0) for sizing half dimension (only w or only h)\n    rt_swear((ws->min_w != 0) == (ws->min_h != 0) &&\n           ws->min_w >= 0 && ws->min_h >= 0,\n          \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f\", ws->min_w, ws->min_h);\n    rt_swear((ws->ini_w != 0) == (ws->ini_h != 0) &&\n           ws->ini_w >= 0 && ws->ini_h >= 0,\n          \"ui_app.window_sizing .ini_w=%.1f .ini_h=%.1f\", ws->ini_w, ws->ini_h);\n    rt_swear((ws->max_w != 0) == (ws->max_h != 0) &&\n           ws->max_w >= 0 && ws->max_h >= 0,\n          \"ui_app.window_sizing .max_w=%.1f .max_h=%.1f\", ws->max_w, ws->max_h);\n    // if max is set then min and ini must be less than max\n    if (ws->max_w != 0 || ws->max_h != 0) {\n        rt_swear(ws->min_w <= ws->max_w && ws->min_h <= ws->max_h,\n            \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f .max_w=%1.f .max_h=%.1f\",\n             ws->min_w, ws->min_h, ws->max_w, ws->max_h);\n        rt_swear(ws->ini_w <= ws->max_w && ws->ini_h <= ws->max_h,\n            \"ui_app.window_sizing .min_w=%.1f .min_h=%.1f .max_w=%1.f .max_h=%.1f\",\n                ws->ini_w, ws->ini_h, ws->max_w, ws->max_h);\n    }\n    const int32_t ini_w = ui_app.in2px(ws->ini_w);\n    const int32_t ini_h = ui_app.in2px(ws->ini_h);\n    int32_t min_w = ws->min_w > 0 ? ui_app.in2px(ws->min_w) : ui_app.work_area.w / 4;\n    int32_t min_h = ws->min_h > 0 ? ui_app.in2px(ws->min_h) : ui_app.work_area.h / 4;\n    // (x, y) (-1, -1) means \"let Windows manager position the window\"\n    ui_rect_t r = {-1, -1,\n                   ini_w > 0 ? ini_w : min_w, ini_h > 0 ? ini_h : min_h};\n    return r;\n}\n\nstatic FILE* ui_app_crash_log;\n\nstatic bool ui_app_write_backtrace(const char* s, int32_t n) {\n    if (n > 0 && s[n - 1] == 0) { n--; }\n    if (n > 0 && ui_app_crash_log != null) {\n        fwrite(s, n, 1, ui_app_crash_log);\n    }\n    return false;\n}\n\nstatic LONG ui_app_exception_filter(EXCEPTION_POINTERS* ep) {\n    char fn[1024];\n    DWORD ex = ep->ExceptionRecord->ExceptionCode; // exception code\n    // T-connector for intercepting rt_debug.output:\n    bool (*tee)(const char* s, int32_t n) = rt_debug.tee;\n    rt_debug.tee = ui_app_write_backtrace;\n    const char* home = rt_files.known_folder(rt_files.folder.home);\n    if (home != null) {\n        const char* name = ui_app.class_name  != null ?\n                           ui_app.class_name : \"ui_app\";\n        rt_str_printf(fn, \"%s\\\\%s_crash_log.txt\", home, name);\n        ui_app_crash_log = fopen(fn, \"w\");\n    }\n    rt_debug.println(null, 0, null,\n        \"To file and issue report copy this log and\");\n    rt_debug.println(null, 0, null,\n        \"paste it here: https://github.com/leok7v/ui/discussions/4\");\n    rt_debug.println(null, 0, null,\n        \"%s exception: %s\", rt_args.basename(), rt_str.error(ex));\n    rt_backtrace_t bt = {{0}};\n    rt_backtrace.context(rt_thread.self(), ep->ContextRecord, &bt);\n    rt_backtrace.trace(&bt, \"*\");\n    rt_backtrace.trace_all_but_self();\n    rt_debug.tee = tee;\n    if (ui_app_crash_log != null) {\n        fclose(ui_app_crash_log);\n        char cmd[1024];\n        rt_str_printf(cmd, \"cmd.exe /c start notepad \\\"%s\\\"\", fn);\n        system(cmd);\n    }\n    return EXCEPTION_CONTINUE_SEARCH;\n}\n\n#undef UI_APP_TEST_POST\n\n#ifdef UI_APP_TEST_POST\n\n// The dispatch_until() is just for testing purposes.\n// Usually rt_work_queue.dispatch(q) will be called inside each\n// iteration of message loop of a dispatch [UI] thread.\n\nstatic void ui_app_test_dispatch_until(rt_work_queue_t* q, int32_t* i,\n        const int32_t n) {\n    while (q->head != null && *i < n) {\n        rt_thread.sleep_for(0.0001); // 100 microseconds\n        rt_work_queue.dispatch(q);\n    }\n    rt_work_queue.flush(q);\n}\n\n// simple way of passing a single pointer to call_later\n\nstatic void ui_app_test_every_100ms(rt_work_t* w) {\n    int32_t* i = (int32_t*)w->data;\n    rt_println(\"i: %d\", *i);\n    (*i)++;\n    w->when = rt_clock.seconds() + 0.100;\n    rt_work_queue.post(w);\n}\n\nstatic void ui_app_test_work_queue_1(void) {\n    rt_work_queue_t queue = {0};\n    // if a single pointer will suffice\n    int32_t i = 0;\n    rt_work_t work = {\n        .queue = &queue,\n        .when  = rt_clock.seconds() + 0.100,\n        .work  = ui_app_test_every_100ms,\n        .data  = &i\n    };\n    rt_work_queue.post(&work);\n    ui_app_test_dispatch_until(&queue, &i, 4);\n}\n\n// extending rt_work_t with extra data:\n\ntypedef struct rt_work_ex_s {\n    union {\n        rt_work_t base;\n        struct rt_work_s;\n    };\n    struct { int32_t a; int32_t b; } s;\n    int32_t i;\n} rt_work_ex_t;\n\nstatic void ui_app_test_every_200ms(rt_work_t* w) {\n    rt_work_ex_t* ex = (rt_work_ex_t*)w;\n    rt_println(\"ex { .i: %d, .s.a: %d .s.b: %d}\", ex->i, ex->s.a, ex->s.b);\n    ex->i++;\n    const int32_t swap = ex->s.a; ex->s.a = ex->s.b; ex->s.b = swap;\n    w->when = rt_clock.seconds() + 0.200;\n    rt_work_queue.post(w);\n}\n\nstatic void ui_app_test_work_queue_2(void) {\n    rt_work_queue_t queue = {0};\n    rt_work_ex_t work = {\n        .queue = &queue,\n        .when  = rt_clock.seconds() + 0.200,\n        .work  = ui_app_test_every_200ms,\n        .data  = null,\n        .s = { .a = 1, .b = 2 },\n        .i = 0\n    };\n    rt_work_queue.post(&work.base);\n    ui_app_test_dispatch_until(&queue, &work.i, 4);\n}\n\nstatic fp64_t ui_app_test_timestamp_0;\nstatic fp64_t ui_app_test_timestamp_2;\nstatic fp64_t ui_app_test_timestamp_3;\nstatic fp64_t ui_app_test_timestamp_4;\n\nstatic void ui_app_test_in_1_second(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_3 = rt_clock.seconds();\n    rt_println(\"ETA 3 seconds\");\n}\n\nstatic void ui_app_test_in_2_seconds(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_2 = rt_clock.seconds();\n    rt_println(\"ETA 2 seconds\");\n    static rt_work_t invoke_in_1_seconds;\n    invoke_in_1_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 1.0, // seconds\n        .work = ui_app_test_in_1_second\n    };\n    ui_app.post(&invoke_in_1_seconds);\n}\n\nstatic void ui_app_test_in_4_seconds(rt_work_t* rt_unused(work)) {\n    ui_app_test_timestamp_4 = rt_clock.seconds();\n    rt_println(\"ETA 4 seconds\");\n//  expected sequence of callbacks:\n//  2:732 ui_app_test_in_2_seconds ETA 2 seconds\n//  3:724 ui_app_test_in_1_second  ETA 3 seconds\n//  4:735 ui_app_test_in_4_seconds ETA 4 seconds\n    fp64_t dt2 = ui_app_test_timestamp_2 - ui_app_test_timestamp_0;\n    fp64_t dt3 = ui_app_test_timestamp_3 - ui_app_test_timestamp_0;\n    fp64_t dt4 = ui_app_test_timestamp_4 - ui_app_test_timestamp_0;\n//  Assuming there were no huge startup delays:\n    swear(1.75 < dt2 < 2.25);\n    swear(2.75 < dt3 < 3.25);\n    swear(3.75 < dt4 < 4.25);\n}\n\nstatic void ui_app_test_post(void) {\n    ui_app_test_work_queue_1();\n    ui_app_test_work_queue_2();\n    rt_println(\"see Output/Timestamps\");\n    static rt_work_t invoke_in_2_seconds;\n    static rt_work_t invoke_in_4_seconds;\n    ui_app_test_timestamp_0 = rt_clock.seconds();\n    invoke_in_2_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 2.0, // seconds\n        .work = ui_app_test_in_2_seconds\n    };\n    invoke_in_4_seconds = (rt_work_t){\n        .queue = null, // &ui_app_queue will be used\n        .when = rt_clock.seconds() + 4.0, // seconds\n        .work = ui_app_test_in_4_seconds\n    };\n    ui_app.post(&invoke_in_4_seconds);\n    ui_app.post(&invoke_in_2_seconds);\n}\n\n#endif\n\nstatic int ui_app_win_main(HINSTANCE instance) {\n    // IDI_ICON 101:\n    ui_app.icon = (ui_icon_t)LoadIconW(instance, MAKEINTRESOURCE(101));\n    ui_app_init_windows();\n    ui_gdi.init();\n    rt_clipboard.put_image = ui_app_clipboard_put_image;\n    ui_app.last_visibility = ui.visibility.defau1t;\n    ui_app_init();\n    int r = 0;\n//  ui_app_dump_dpi();\n    // It is possible (but not trivial) to ask DWM to create taller tittle bar:\n    // https://learn.microsoft.com/en-us/windows/win32/dwm/customframe\n    // TODO: if any app need to make to app store they will probably ask for it\n    // \"wr\" Window Rect in pixels: default is -1,-1, ini_w, ini_h\n    ui_rect_t wr = ui_app_window_initial_rectangle();\n    ui_app.caption_height = (int32_t)GetSystemMetricsForDpi(SM_CYCAPTION,\n                                (uint32_t)ui_app.dpi.process);\n    ui_app.border.w = (int32_t)GetSystemMetricsForDpi(SM_CXSIZEFRAME,\n                                (uint32_t)ui_app.dpi.process);\n    ui_app.border.h = (int32_t)GetSystemMetricsForDpi(SM_CYSIZEFRAME,\n                                (uint32_t)ui_app.dpi.process);\n\n    if (ui_app.no_decor) {\n        // border is too think (5 pixels) narrow down to 3x3\n        const int32_t max_border = ui_app.dpi.window <= 100 ? 1 :\n            (ui_app.dpi.window >= 192 ? 3 : 2);\n        ui_app.border.w = rt_min(max_border, ui_app.border.w);\n        ui_app.border.h = rt_min(max_border, ui_app.border.h);\n    }\n//  rt_println(\"frame: %d,%d caption_height: %d\", ui_app.border.w, ui_app.border.h, ui_app.caption_height);\n    // TODO: use AdjustWindowRectEx instead\n    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-adjustwindowrectex\n    wr.x -= ui_app.border.w;\n    wr.w += ui_app.border.w * 2;\n    wr.y -= ui_app.border.h + ui_app.caption_height;\n    wr.h += ui_app.border.h * 2 + ui_app.caption_height;\n    if (!ui_app_load_window_pos(&wr, &ui_app.last_visibility)) {\n        // first time - center window\n        wr.x = ui_app.work_area.x + (ui_app.work_area.w - wr.w) / 2;\n        wr.y = ui_app.work_area.y + (ui_app.work_area.h - wr.h) / 2;\n        ui_app_bring_window_inside_monitor(&ui_app.mrc, &wr);\n    }\n    ui_app.root->state.hidden = true; // start with ui hidden\n    ui_app.root->fm = &ui_app.fm.prop.normal;\n    ui_app.root->w = wr.w - ui_app.border.w * 2;\n    ui_app.root->h = wr.h - ui_app.border.h * 2 - ui_app.caption_height;\n    ui_app_layout_dirty = true; // layout will be done before first paint\n    rt_not_null(ui_app.class_name);\n    ui_app_wt = (rt_event_t)CreateWaitableTimerA(null, false, null);\n    rt_thread_t alarm  = rt_thread.start(ui_app_alarm_thread, null);\n    if (!ui_app.no_ui) {\n        ui_app_create_window(wr);\n        ui_app_init_fonts(ui_app.dpi.window);\n        rt_thread_t redraw = rt_thread.start(ui_app_redraw_thread, null);\n        #ifdef UI_APP_TEST_POST\n            ui_app_test_post();\n        #endif\n        r = ui_app_message_loop();\n        // ui_app.fini() must be called before ui_app_dispose()\n        if (ui_app.fini != null) { ui_app.fini(); }\n        rt_event.set(ui_app_event_quit);\n        rt_thread.join(redraw, -1);\n        ui_app_dispose();\n        if (r == 0 && ui_app.exit_code != 0) { r = ui_app.exit_code; }\n    } else {\n        r = ui_app.main();\n        if (ui_app.fini != null) { ui_app.fini(); }\n    }\n    rt_event.set(ui_app_event_quit);\n    rt_thread.join(alarm, -1);\n    rt_event.dispose(ui_app_event_quit);\n    ui_app_event_quit = null;\n    rt_event.dispose(ui_app_wt);\n    ui_app_wt = null;\n    ui_gdi.fini();\n    return r;\n}\n\n#pragma warning(disable: 28251) // inconsistent annotations\n\nint WINAPI WinMain(HINSTANCE instance, HINSTANCE rt_unused(previous),\n        char* rt_unused(command), int show) {\n    SetUnhandledExceptionFilter(ui_app_exception_filter);\n    const COINIT co_init = COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY;\n    rt_fatal_if_error(CoInitializeEx(0, co_init));\n    SetConsoleCP(CP_UTF8);\n    // Expected manifest.xml containing UTF-8 code page\n    // for TranslateMessage and WM_CHAR to deliver UTF-8 characters\n    // see:\n    // https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page\n    // .rc file must have:\n    // 1 RT_MANIFEST \"manifest.xml\"\n    if (GetACP() != 65001) {\n        rt_println(\"codepage: %d UTF-8 will not be supported\", GetACP());\n    }\n    // at the moment of writing there is no API call to inform Windows about process\n    // preferred codepage except manifest.xml file in resource #1.\n    // Absence of manifest.xml will result to ancient and useless ANSI 1252 codepage\n    // TODO: may need to change CreateWindowA() to CreateWindowW() and\n    // translate UTF16 to UTF8\n    ui_app.tid = rt_thread.id();\n    rt_nls.init();\n    ui_app.visibility = show;\n    rt_args.WinMain();\n    int32_t r = ui_app_win_main(instance);\n    rt_args.fini();\n    return r;\n}\n\nint main(int argc, const char* argv[], const char** envp) {\n    SetUnhandledExceptionFilter(ui_app_exception_filter);\n    rt_fatal_if_error(CoInitializeEx(0, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY));\n    rt_args.main(argc, argv, envp);\n    rt_nls.init();\n    ui_app.tid = rt_thread.id();\n    int r = ui_app.main();\n    rt_args.fini();\n    return r;\n}\n\n#pragma pop_macro(\"ui_app_canvas\")\n#pragma pop_macro(\"ui_app_window\")\n\n#pragma comment(lib, \"comctl32\")\n#pragma comment(lib, \"comdlg32\")\n#pragma comment(lib, \"dwmapi\")\n#pragma comment(lib, \"gdi32\")\n#pragma comment(lib, \"msimg32\")\n#pragma comment(lib, \"shcore\")\n#pragma comment(lib, \"uxtheme\")\n"
  },
  {
    "path": "src/ui/ui_button.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic void ui_button_every_100ms(ui_view_t* v) { // every 100ms\n    if (!v->state.hidden) {\n        v->p.armed_until = 0;\n        v->state.armed = false;\n    } else if (v->p.armed_until != 0 && ui_app.now > v->p.armed_until) {\n        v->p.armed_until = 0;\n        v->state.armed = false;\n        ui_view.invalidate(v, null);\n    }\n    if (v->p.armed_until != 0) { ui_app.show_hint(null, -1, -1, 0); }\n}\n\nstatic void ui_button_paint(ui_view_t* v) {\n    bool pressed = (v->state.armed ^ v->state.pressed) == 0;\n    if (v->p.armed_until != 0) { pressed = true; }\n    const int32_t w = v->w;\n    const int32_t h = v->h;\n    const int32_t x = v->x;\n    const int32_t y = v->y;\n    const int32_t r = (0x1 | rt_max(3, v->fm->em.h / 4));  // odd radius\n    const fp32_t d = ui_theme.is_app_dark() ? 0.50f : 0.25f;\n    ui_color_t d0 = ui_colors.darken(v->background, d);\n    const fp32_t d2 = d / 2;\n    if (v->flat) {\n        if (v->state.hover) {\n            ui_color_t d1 = ui_theme.is_app_dark() ?\n                    ui_colors.lighten(v->background, d2) :\n                    ui_colors.darken(v->background,  d2);\n            if (!pressed) {\n                ui_gdi.gradient(x, y, w, h, d0, d1, true);\n            } else {\n                ui_gdi.gradient(x, y, w, h, d1, d0, true);\n            }\n        }\n    } else {\n        // `bc` border color\n        ui_color_t bc = ui_colors.get_color(ui_color_id_gray_text);\n        if (v->state.armed) { bc = ui_colors.lighten(bc, 0.125f); }\n        if (ui_view.is_disabled(v)) { bc = ui_color_rgb(30, 30, 30); } // TODO: hardcoded\n        if (v->state.hover && !v->state.armed) {\n            bc = ui_colors.get_color(ui_color_id_hot_tracking);\n        }\n        ui_color_t d1 = ui_colors.darken(v->background, d2);\n        ui_color_t fc = ui_colors.interpolate(d0, d1, 0.5f); // fill color\n        if (v->state.armed) {\n            fc = ui_colors.lighten(fc, 0.250f);\n        } else if (v->state.hover) {\n            fc = ui_colors.darken(fc, 0.250f);\n        }\n        ui_gdi.rounded(v->x, v->y, v->w, v->h, r, bc, fc);\n    }\n    const int32_t tx = v->x + v->text.xy.x;\n    const int32_t ty = v->y + v->text.xy.y;\n    if (v->icon == null) {\n        ui_color_t c = v->color;\n        if (v->state.hover && !v->state.armed) {\n            c = ui_theme.is_app_dark() ? ui_color_rgb(0xFF, 0xE0, 0xE0) :\n                                         ui_color_rgb(0x00, 0x40, 0xFF);\n        }\n        if (ui_view.is_disabled(v)) { c = ui_colors.get_color(ui_color_id_gray_text); }\n        if (v->debug.paint.fm) {\n            ui_view.debug_paint_fm(v);\n        }\n        const ui_gdi_ta_t ta = { .fm = v->fm, .color = c };\n        ui_gdi.text(&ta, tx, ty, \"%s\", ui_view.string(v));\n    } else {\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        const ui_wh_t i_wh = { .w = v->w - i.left - i.right,\n                               .h = v->h - i.top - i.bottom };\n        // TODO: icon text alignment\n        ui_gdi.icon(tx, ty + v->text.xy.y, i_wh.w, i_wh.h, v->icon);\n    }\n}\n\nstatic void ui_button_callback(ui_button_t* b) {\n    // for flip buttons the state of the button flips\n    // *before* callback.\n    if (b->flip) { b->state.pressed = !b->state.pressed; }\n    const bool pressed = b->state.pressed;\n    if (b->callback != null) { b->callback(b); }\n    if (pressed != b->state.pressed) {\n        if (b->flip) { // warn the client of strange logic:\n            rt_println(\"strange flip the button with button.flip: true\");\n            // if client wants to flip pressed state manually it\n            // should do it for the button.flip = false\n        }\n//      rt_println(\"disarmed immediately\");\n        b->p.armed_until = 0;\n        b->state.armed = false;\n    } else {\n        if (b->flip) {\n//          rt_println(\"disarmed immediately\");\n            b->p.armed_until = 0;\n            b->state.armed = false;\n        } else {\n//          rt_println(\"will disarm in 1/4 seconds\");\n            b->p.armed_until = ui_app.now + 0.250;\n        }\n    }\n}\n\nstatic void ui_button_trigger(ui_view_t* v) {\n    ui_button_t* b = (ui_button_t*)v;\n    v->state.armed = true;\n    ui_view.invalidate(v, null);\n    ui_button_callback(b);\n}\n\nstatic void ui_button_character(ui_view_t* v, const char* utf8) {\n    char ch = utf8[0]; // TODO: multibyte utf8 shortcuts?\n    if (ui_view.is_shortcut_key(v, ch)) {\n        ui_button_trigger(v);\n    }\n}\n\nstatic bool ui_button_key_pressed(ui_view_t* v, int64_t key) {\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    const bool trigger = ui_app.alt && ui_view.is_shortcut_key(v, key);\n    if (trigger) { ui_button_trigger(v); }\n    return trigger; // swallow if true\n}\n\nstatic bool ui_button_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    // 'ix' ignored - button index acts on any mouse button\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        ui_view.invalidate(v, null); // always on any press/release inside\n        ui_button_t* b = (ui_button_t*)v;\n        if (pressed && b->flip) {\n            if (b->flip) { ui_button_callback(b); }\n        } else if (pressed) {\n            v->state.armed = true;\n        } else { // released\n            if (!b->flip) { ui_button_callback(b); }\n        }\n    }\n    return pressed && inside; // swallow clicks inside\n}\n\nvoid ui_view_init_button(ui_view_t* v) {\n    rt_assert(v->type == ui_view_button);\n    v->tap           = ui_button_tap;\n    v->paint         = ui_button_paint;\n    v->character     = ui_button_character;\n    v->every_100ms   = ui_button_every_100ms;\n    v->key_pressed   = ui_button_key_pressed;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    if (v->debug.id == null) { v->debug.id = \"#button\"; }\n}\n\nvoid ui_button_init(ui_button_t* b, const char* label, fp32_t ems,\n        void (*callback)(ui_button_t* b)) {\n    b->type = ui_view_button;\n    ui_view.set_text(b, \"%s\", label);\n    b->callback = callback;\n    b->min_w_em = ems;\n    ui_view_init_button(b);\n}\n"
  },
  {
    "path": "src/ui/ui_caption.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\n#pragma push_macro(\"ui_caption_glyph_rest\")\n#pragma push_macro(\"ui_caption_glyph_menu\")\n#pragma push_macro(\"ui_caption_glyph_dark\")\n#pragma push_macro(\"ui_caption_glyph_light\")\n#pragma push_macro(\"ui_caption_glyph_mini\")\n#pragma push_macro(\"ui_caption_glyph_maxi\")\n#pragma push_macro(\"ui_caption_glyph_full\")\n#pragma push_macro(\"ui_caption_glyph_quit\")\n\n#define ui_caption_glyph_rest  rt_glyph_white_square_with_upper_right_quadrant // instead of rt_glyph_desktop_window\n#define ui_caption_glyph_menu  rt_glyph_trigram_for_heaven\n#define ui_caption_glyph_dark  rt_glyph_crescent_moon\n#define ui_caption_glyph_light rt_glyph_white_sun_with_rays\n#define ui_caption_glyph_mini  rt_glyph_minimize\n#define ui_caption_glyph_maxi  rt_glyph_white_square_with_lower_left_quadrant // instead of rt_glyph_maximize\n#define ui_caption_glyph_full  rt_glyph_square_four_corners\n#define ui_caption_glyph_quit  rt_glyph_cancellation_x\n\nstatic void ui_caption_toggle_full(void) {\n    ui_app.full_screen(!ui_app.is_full_screen);\n    ui_caption.view.state.hidden = ui_app.is_full_screen;\n    ui_app.request_layout();\n}\n\nstatic void ui_caption_esc_full_screen(ui_view_t* v, const char utf8[]) {\n    rt_swear(v == ui_caption.view.parent);\n    // TODO: inside ui_app.c instead of here?\n    if (utf8[0] == 033 && ui_app.is_full_screen) { ui_caption_toggle_full(); }\n}\n\nstatic void ui_caption_quit(ui_button_t* rt_unused(b)) {\n    ui_app.close();\n}\n\nstatic void ui_caption_mini(ui_button_t* rt_unused(b)) {\n    ui_app.show_window(ui.visibility.minimize);\n}\n\nstatic void ui_caption_mode_appearance(void) {\n    if (ui_theme.is_app_dark()) {\n        ui_view.set_text(&ui_caption.mode, \"%s\", ui_caption_glyph_light);\n        rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Light Mode\"));\n    } else {\n        ui_view.set_text(&ui_caption.mode, \"%s\", ui_caption_glyph_dark);\n        rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Dark Mode\"));\n    }\n}\n\nstatic void ui_caption_mode(ui_button_t* rt_unused(b)) {\n    bool was_dark = ui_theme.is_app_dark();\n    ui_app.light_mode =  was_dark;\n    ui_app.dark_mode  = !was_dark;\n    ui_theme.refresh();\n    ui_caption_mode_appearance();\n}\n\nstatic void ui_caption_maximize_or_restore(void) {\n    ui_view.set_text(&ui_caption.maxi, \"%s\",\n        ui_app.is_maximized() ?\n        ui_caption_glyph_rest : ui_caption_glyph_maxi);\n    rt_str_printf(ui_caption.maxi.hint, \"%s\",\n        ui_app.is_maximized() ?\n        rt_nls.str(\"Restore\") : rt_nls.str(\"Maximize\"));\n    // non-decorated windows on Win32 are \"popup\" style\n    // that cannot be maximized. Full screen will serve\n    // the purpose of maximization.\n    ui_caption.maxi.state.hidden = ui_app.no_decor;\n}\n\nstatic void ui_caption_maxi(ui_button_t* rt_unused(b)) {\n    if (!ui_app.is_maximized()) {\n        ui_app.show_window(ui.visibility.maximize);\n    } else if (ui_app.is_maximized() || ui_app.is_minimized()) {\n        ui_app.show_window(ui.visibility.restore);\n    }\n    ui_caption_maximize_or_restore();\n}\n\nstatic void ui_caption_full(ui_button_t* rt_unused(b)) {\n    ui_caption_toggle_full();\n}\n\nstatic int64_t ui_caption_hit_test(const ui_view_t* v, ui_point_t pt) {\n    rt_swear(v == &ui_caption.view);\n    rt_assert(ui_view.inside(v, &pt));\n//  rt_println(\"%d,%d ui_caption.icon: %d,%d %dx%d inside: %d\",\n//      x, y,\n//      ui_caption.icon.x, ui_caption.icon.y,\n//      ui_caption.icon.w, ui_caption.icon.h,\n//      ui_view.inside(&ui_caption.icon, &pt));\n    if (ui_app.is_full_screen) {\n        return ui.hit_test.client;\n    } else if (!ui_caption.icon.state.hidden &&\n                ui_view.inside(&ui_caption.icon, &pt)) {\n        return ui.hit_test.system_menu;\n    } else {\n        ui_view_for_each(&ui_caption.view, c, {\n            bool ignore = c->type == ui_view_stack ||\n                          c->type == ui_view_spacer ||\n                          c->type == ui_view_label;\n            if (!ignore && ui_view.inside(c, &pt)) {\n                return ui.hit_test.client;\n            }\n        });\n        return ui.hit_test.caption;\n    }\n}\n\nstatic ui_color_t ui_caption_color(void) {\n    ui_color_t c = ui_app.is_active() ?\n        ui_colors.get_color(ui_color_id_active_title) :\n        ui_colors.get_color(ui_color_id_inactive_title);\n    return c;\n}\n\nstatic const ui_margins_t ui_caption_button_button_padding =\n    { .left  = 0.25,  .top    = 0.0,\n      .right = 0.25,  .bottom = 0.0};\n\nstatic void ui_caption_button_measure(ui_view_t* v) {\n    rt_assert(v->type == ui_view_button);\n    ui_view.measure_control(v);\n    const int32_t dx = ui_app.caption_height - v->w;\n    const int32_t dy = ui_app.caption_height - v->h;\n    v->w += dx;\n    v->h += dy;\n    v->text.xy.x += dx / 2;\n    v->text.xy.y += dy / 2;\n    v->padding = ui_caption_button_button_padding;\n}\n\nstatic void ui_caption_button_icon_paint(ui_view_t* v) {\n    int32_t w = v->w;\n    int32_t h = v->h;\n    while (h > 16 && (h & (h - 1)) != 0) { h--; }\n    w = h;\n    int32_t dx = (v->w - w) / 2;\n    int32_t dy = (v->h - h) / 2;\n    ui_gdi.icon(v->x + dx, v->y + dy, w, h, v->icon);\n}\n\nstatic void ui_caption_prepare(ui_view_t* rt_unused(v)) {\n    ui_caption.title.state.hidden = false;\n}\n\nstatic void ui_caption_measured(ui_view_t* v) {\n    // remeasure all child buttons with hard override:\n    int32_t w = 0;\n    ui_view_for_each(v, it, {\n        if (it->type == ui_view_button) {\n            it->fm = &ui_app.fm.mono.normal;\n            it->flat = true;\n            ui_caption_button_measure(it);\n        }\n        if (!it->state.hidden) {\n            const ui_ltrb_t p = ui_view.margins(it, &it->padding);\n            w += it->w + p.left + p.right;\n        }\n    });\n    const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n    w += p.left + p.right;\n    // do not show title if there is not enough space\n    ui_caption.title.state.hidden = w > ui_app.root->w;\n    v->w = ui_app.root->w;\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    v->h = insets.top + ui_app.caption_height + insets.bottom;\n}\n\nstatic void ui_caption_composed(ui_view_t* v) {\n    v->x = ui_app.root->x;\n    v->y = ui_app.root->y;\n}\n\nstatic void ui_caption_paint(ui_view_t* v) {\n    ui_color_t background = ui_caption_color();\n    ui_gdi.fill(v->x, v->y, v->w, v->h, background);\n}\n\nstatic void ui_caption_init(ui_view_t* v) {\n    rt_swear(v == &ui_caption.view, \"caption is a singleton\");\n    ui_view_init_span(v);\n    ui_caption.view.insets = (ui_margins_t){ 0.125, 0.0, 0.125, 0.0 };\n    ui_caption.view.state.hidden = false;\n    v->parent->character = ui_caption_esc_full_screen; // ESC for full screen\n    ui_view.add(&ui_caption.view,\n        &ui_caption.icon,\n        &ui_caption.menu,\n        &ui_caption.title,\n        &ui_caption.spacer,\n        &ui_caption.mode,\n        &ui_caption.mini,\n        &ui_caption.maxi,\n        &ui_caption.full,\n        &ui_caption.quit,\n        null);\n    ui_caption.view.color_id = ui_color_id_window_text;\n    static const ui_margins_t p0 = { .left  = 0.0,   .top    = 0.0,\n                                     .right = 0.0,   .bottom = 0.0};\n    static const ui_margins_t pd = { .left  = 0.25,  .top    = 0.0,\n                                     .right = 0.25,  .bottom = 0.0};\n    static const ui_margins_t in = { .left  = 0.0,   .top    = 0.0,\n                                     .right = 0.0,   .bottom = 0.0};\n    ui_view_for_each(&ui_caption.view, c, {\n        c->fm = &ui_app.fm.prop.normal;\n        c->color_id = ui_caption.view.color_id;\n        if (c->type != ui_view_button) {\n            c->padding = pd;\n        }\n        c->insets  = in;\n        c->h = ui_app.caption_height;\n        c->min_w_em = 0.5f;\n        c->min_h_em = 0.5f;\n    });\n    rt_str_printf(ui_caption.menu.hint, \"%s\", rt_nls.str(\"Menu\"));\n    rt_str_printf(ui_caption.mode.hint, \"%s\", rt_nls.str(\"Switch to Light Mode\"));\n    rt_str_printf(ui_caption.mini.hint, \"%s\", rt_nls.str(\"Minimize\"));\n    rt_str_printf(ui_caption.maxi.hint, \"%s\", rt_nls.str(\"Maximize\"));\n    rt_str_printf(ui_caption.full.hint, \"%s\", rt_nls.str(\"Full Screen (ESC to restore)\"));\n    rt_str_printf(ui_caption.quit.hint, \"%s\", rt_nls.str(\"Close\"));\n    ui_caption.icon.icon     = ui_app.icon;\n    ui_caption.icon.padding  = p0;\n    ui_caption.icon.paint    = ui_caption_button_icon_paint;\n    ui_caption.view.align    = ui.align.left;\n    ui_caption.view.prepare  = ui_caption_prepare;\n    ui_caption.view.measured = ui_caption_measured;\n    ui_caption.view.composed = ui_caption_composed;\n    ui_view.set_text(&ui_caption.view, \"#ui_caption\"); // for debugging\n    ui_caption_maximize_or_restore();\n    ui_caption.view.paint = ui_caption_paint;\n    ui_caption_mode_appearance();\n    ui_caption.icon.debug.id = \"#caption.icon\";\n    ui_caption.menu.debug.id = \"#caption.menu\";\n    ui_caption.mode.debug.id = \"#caption.mode\";\n    ui_caption.mini.debug.id = \"#caption.mini\";\n    ui_caption.maxi.debug.id = \"#caption.maxi\";\n    ui_caption.full.debug.id = \"#caption.full\";\n    ui_caption.quit.debug.id = \"#caption.quit\";\n    ui_caption.title.debug.id  = \"#caption.title\";\n    ui_caption.spacer.debug.id = \"#caption.spacer\";\n\n}\n\nui_caption_t ui_caption =  {\n    .view = {\n        .type     = ui_view_span,\n        .fm       = &ui_app.fm.prop.normal,\n        .init     = ui_caption_init,\n        .hit_test = ui_caption_hit_test,\n        .state.hidden = true\n    },\n    .icon   = ui_button(rt_glyph_nbsp, 0.0, null),\n    .title  = ui_label(0, \"\"),\n    .spacer = ui_view(spacer),\n    .menu   = ui_button(ui_caption_glyph_menu, 0.0, null),\n    .mode   = ui_button(ui_caption_glyph_mini, 0.0, ui_caption_mode),\n    .mini   = ui_button(ui_caption_glyph_mini, 0.0, ui_caption_mini),\n    .maxi   = ui_button(ui_caption_glyph_maxi, 0.0, ui_caption_maxi),\n    .full   = ui_button(ui_caption_glyph_full, 0.0, ui_caption_full),\n    .quit   = ui_button(ui_caption_glyph_quit, 0.0, ui_caption_quit),\n};\n\n#pragma pop_macro(\"ui_caption_glyph_rest\")\n#pragma pop_macro(\"ui_caption_glyph_menu\")\n#pragma pop_macro(\"ui_caption_glyph_dark\")\n#pragma pop_macro(\"ui_caption_glyph_light\")\n#pragma pop_macro(\"ui_caption_glyph_mini\")\n#pragma pop_macro(\"ui_caption_glyph_maxi\")\n#pragma pop_macro(\"ui_caption_glyph_full\")\n#pragma pop_macro(\"ui_caption_glyph_quit\")\n"
  },
  {
    "path": "src/ui/ui_colors.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic inline uint8_t ui_color_clamp_uint8(fp64_t value) {\n    return value < 0 ? 0 : (value > 255 ? 255 : (uint8_t)value);\n}\n\nstatic inline fp64_t ui_color_fp64_min(fp64_t x, fp64_t y) { return x < y ? x : y; }\n\nstatic inline fp64_t ui_color_fp64_max(fp64_t x, fp64_t y) { return x > y ? x : y; }\n\nstatic void ui_color_rgb_to_hsi(fp64_t r, fp64_t g, fp64_t b, fp64_t *h, fp64_t *s, fp64_t *i) {\n    r /= 255.0;\n    g /= 255.0;\n    b /= 255.0;\n    fp64_t min_val = ui_color_fp64_min(r, ui_color_fp64_min(g, b));\n    *i = (r + g + b) / 3;\n    fp64_t chroma = ui_color_fp64_max(r, ui_color_fp64_max(g, b)) - min_val;\n    if (chroma == 0) {\n        *h = 0;\n        *s = 0;\n    } else {\n        *s = 1 - min_val / *i;\n        if (*i > 0) { *s = chroma / (*i * 3); }\n        if (r == ui_color_fp64_max(r, ui_color_fp64_max(g, b))) {\n            *h = (g - b) / chroma + (g < b ? 6 : 0);\n        } else if (g == ui_color_fp64_max(r, ui_color_fp64_max(g, b))) {\n            *h = (b - r) / chroma + 2;\n        } else {\n            *h = (r - g) / chroma + 4;\n        }\n        *h *= 60;\n    }\n}\n\nstatic ui_color_t ui_color_hsi_to_rgb(fp64_t h, fp64_t s, fp64_t i, uint8_t a) {\n    h /= 60.0;\n    fp64_t f = h - (int32_t)h;\n    fp64_t p = i * (1 - s);\n    fp64_t q = i * (1 - s * f);\n    fp64_t t = i * (1 - s * (1 - f));\n    fp64_t r = 0, g = 0, b = 0;\n    switch ((int32_t)h) {\n        case 0:\n        case 6: r = i * 255; g = t * 255; b = p * 255; break;\n        case 1: r = q * 255; g = i * 255; b = p * 255; break;\n        case 2: r = p * 255; g = i * 255; b = t * 255; break;\n        case 3: r = p * 255; g = q * 255; b = i * 255; break;\n        case 4: r = t * 255; g = p * 255; b = i * 255; break;\n        case 5: r = i * 255; g = p * 255; b = q * 255; break;\n        default: rt_swear(false); break;\n    }\n    rt_assert(0 <= r && r <= 255);\n    rt_assert(0 <= g && g <= 255);\n    rt_assert(0 <= b && b <= 255);\n    return ui_color_rgba((uint8_t)r, (uint8_t)g, (uint8_t)b, a);\n}\n\nstatic ui_color_t ui_color_brightness(ui_color_t c, fp32_t multiplier) {\n    fp64_t h, s, i;\n    ui_color_rgb_to_hsi(ui_color_r(c), ui_color_g(c), ui_color_b(c), &h, &s, &i);\n    i = ui_color_fp64_max(0, ui_color_fp64_min(1, i * (fp64_t)multiplier));\n    return ui_color_hsi_to_rgb(h, s, i, ui_color_a(c));\n}\n\nstatic ui_color_t ui_color_saturation(ui_color_t c, fp32_t multiplier) {\n    fp64_t h, s, i;\n    ui_color_rgb_to_hsi(ui_color_r(c), ui_color_g(c), ui_color_b(c), &h, &s, &i);\n    s = ui_color_fp64_max(0, ui_color_fp64_min(1, s * (fp64_t)multiplier));\n    return ui_color_hsi_to_rgb(h, s, i, ui_color_a(c));\n}\n\n// Using the ui_color_interpolate function to blend colors toward\n// black or white can effectively adjust brightness and saturation,\n// offering more flexibility  and potentially better results in\n// terms of visual transitions between colors.\n\nstatic ui_color_t ui_color_interpolate(ui_color_t c0, ui_color_t c1,\n        fp32_t multiplier) {\n    rt_assert(0.0f < multiplier && multiplier < 1.0f);\n    fp64_t h0, s0, i0, h1, s1, i1;\n    ui_color_rgb_to_hsi(ui_color_r(c0), ui_color_g(c0), ui_color_b(c0),\n                       &h0, &s0, &i0);\n    ui_color_rgb_to_hsi(ui_color_r(c1), ui_color_g(c1), ui_color_b(c1),\n                       &h1, &s1, &i1);\n    fp64_t h = h0 + (h1 - h0) * (fp64_t)multiplier;\n    fp64_t s = s0 + (s1 - s0) * (fp64_t)multiplier;\n    fp64_t i = i0 + (i1 - i0) * (fp64_t)multiplier;\n    // Interpolate alphas only if differ\n    uint8_t a0 = ui_color_a(c0);\n    uint8_t a1 = ui_color_a(c1);\n    uint8_t a = a0 == a1 ? a0 : ui_color_clamp_uint8(a0 + (a1 - a0) * (fp64_t)multiplier);\n    return ui_color_hsi_to_rgb(h, s, i, a);\n}\n\n// Helper to get a neutral gray with the same intensity\n\nstatic ui_color_t ui_color_gray_with_same_intensity(ui_color_t c) {\n    uint8_t intensity = (ui_color_r(c) + ui_color_g(c) + ui_color_b(c)) / 3;\n    return ui_color_rgba(intensity, intensity, intensity, ui_color_a(c));\n}\n\n// Adjust brightness by interpolating towards black or white\n// using interpolation:\n//\n// To darken the color: Interpolate between\n// the color and black (rgba(0,0,0,255)).\n//\n// To lighten the color: Interpolate between\n// the color and white (rgba(255,255,255,255)).\n//\n// This approach allows you to manipulate the\n// brightness by specifying how close the color\n// should be to either black or white,\n// providing a smooth transition.\n\nstatic ui_color_t ui_color_adjust_brightness(ui_color_t c,\n        fp32_t multiplier, bool lighten) {\n    ui_color_t target = lighten ?\n        ui_color_rgba(255, 255, 255, ui_color_a(c)) :\n        ui_color_rgba(  0,   0,   0, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\n\nstatic ui_color_t ui_color_lighten(ui_color_t c, fp32_t multiplier) {\n    const ui_color_t target = ui_color_rgba(255, 255, 255, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\nstatic ui_color_t ui_color_darken(ui_color_t c, fp32_t multiplier) {\n    const ui_color_t target = ui_color_rgba(0, 0, 0, ui_color_a(c));\n    return ui_color_interpolate(c, target, multiplier);\n}\n\n// Adjust saturation by interpolating towards a gray of the same intensity\n//\n// To adjust saturation, the approach is similar but slightly\n// more nuanced because saturation involves both the color's\n// purity and its brightness:\n\nstatic ui_color_t ui_color_adjust_saturation(ui_color_t c,\n        fp32_t multiplier) {\n    ui_color_t gray = ui_color_gray_with_same_intensity(c);\n    return ui_color_interpolate(c, gray, 1 - multiplier);\n}\n\nstatic struct {\n    const char* name;\n    ui_color_t  dark;\n    ui_color_t  light;\n} ui_theme_colors[] = { // empirical\n    { .name = \"Undefiled\"        ,.dark = ui_color_undefined, .light = ui_color_undefined },\n    { .name = \"ActiveTitle\"      ,.dark = 0x001F1F1F, .light = 0x00D1B499 },\n    { .name = \"ButtonFace\"       ,.dark = 0x00333333, .light = 0x00F0F0F0 },\n    { .name = \"ButtonText\"       ,.dark = 0x00C8C8C8, .light = 0x00161616 },\n//  { .name = \"ButtonText\"       ,.dark = 0x00F6F3EE, .light = 0x00000000 },\n    { .name = \"GrayText\"         ,.dark = 0x00666666, .light = 0x006D6D6D },\n    { .name = \"Hilight\"          ,.dark = 0x00626262, .light = 0x00D77800 },\n    { .name = \"HilightText\"      ,.dark = 0x00000000, .light = 0x00FFFFFF },\n    { .name = \"HotTrackingColor\" ,.dark = 0x00B16300, .light = 0x00FF0000 }, // automatic Win11 \"accent\" ABRG: 0xFFB16300\n//  { .name = \"HotTrackingColor\" ,.dark = 0x00B77878, .light = 0x00CC6600 },\n    { .name = \"InactiveTitle\"    ,.dark = 0x002B2B2B, .light = 0x00DBCDBF },\n    { .name = \"InactiveTitleText\",.dark = 0x00969696, .light = 0x00000000 },\n    { .name = \"MenuHilight\"      ,.dark = 0x00002642, .light = 0x00FF9933 },\n    { .name = \"TitleText\"        ,.dark = 0x00FFFFFF, .light = 0x00000000 },\n//  { .name = \"Window\"           ,.dark = 0x00000000, .light = 0x00FFFFFF }, // too contrast\n//  { .name = \"Window\"           ,.dark = 0x00121212, .light = 0x00E0E0E0 },\n    { .name = \"Window\"           ,.dark = 0x002E2E2E, .light = 0x00E0E0E0 },\n    { .name = \"WindowText\"       ,.dark = 0x00FFFFFF, .light = 0x00000000 },\n};\n\n// TODO: add\n// Accent Color BGR: B16300  RGB: 0063B1 light blue\n// [HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM]\n// \"AccentColor\"=dword:ffb16300\n// Windows used as accent almost on everything\n// see here: https://github.com/leok7v/ui/discussions/5\n\n\nstatic ui_color_t ui_colors_get_color(int32_t color_id) {\n    // SysGetColor() does not work on Win10\n    rt_swear(0 < color_id && color_id < rt_countof(ui_theme_colors));\n    return ui_theme.is_app_dark() ?\n           ui_theme_colors[color_id].dark :\n           ui_theme_colors[color_id].light;\n}\n\nui_colors_if ui_colors = {\n    .get_color                = ui_colors_get_color,\n    .rgb_to_hsi               = ui_color_rgb_to_hsi,\n    .hsi_to_rgb               = ui_color_hsi_to_rgb,\n    .interpolate              = ui_color_interpolate,\n    .gray_with_same_intensity = ui_color_gray_with_same_intensity,\n    .lighten                  = ui_color_lighten,\n    .darken                   = ui_color_darken,\n    .adjust_saturation        = ui_color_adjust_saturation,\n    .multiply_brightness      = ui_color_brightness,\n    .multiply_saturation      = ui_color_saturation,\n    .transparent      = ui_color_transparent,\n    .none             = (ui_color_t)0xFFFFFFFFU, // aka CLR_INVALID in wingdi\n    .text             = ui_color_rgb(240, 231, 220),\n    .white            = ui_color_rgb(255, 255, 255),\n    .black            = ui_color_rgb(0,     0,   0),\n    .red              = ui_color_rgb(255,   0,   0),\n    .green            = ui_color_rgb(0,   255,   0),\n    .blue             = ui_color_rgb(0,   0,   255),\n    .yellow           = ui_color_rgb(255, 255,   0),\n    .cyan             = ui_color_rgb(0,   255, 255),\n    .magenta          = ui_color_rgb(255,   0, 255),\n    .gray             = ui_color_rgb(128, 128, 128),\n    // tone down RGB colors:\n    .tone_white       = ui_color_rgb(164, 164, 164),\n    .tone_red         = ui_color_rgb(192,  64,  64),\n    .tone_green       = ui_color_rgb(64,  192,  64),\n    .tone_blue        = ui_color_rgb(64,   64, 192),\n    .tone_yellow      = ui_color_rgb(192, 192,  64),\n    .tone_cyan        = ui_color_rgb(64,  192, 192),\n    .tone_magenta     = ui_color_rgb(192,  64, 192),\n    // miscellaneous:\n    .orange           = ui_color_rgb(255, 165,   0), // 0xFFA500\n    .dark_green          = ui_color_rgb(  1,  50,  32), // 0x013220\n    .pink             = ui_color_rgb(255, 192, 203), // 0xFFC0CB\n    .ochre            = ui_color_rgb(204, 119,  34), // 0xCC7722\n    .gold             = ui_color_rgb(255, 215,   0), // 0xFFD700\n    .teal             = ui_color_rgb(  0, 128, 128), // 0x008080\n    .wheat            = ui_color_rgb(245, 222, 179), // 0xF5DEB3\n    .tan              = ui_color_rgb(210, 180, 140), // 0xD2B48C\n    .brown            = ui_color_rgb(165,  42,  42), // 0xA52A2A\n    .maroon           = ui_color_rgb(128,   0,   0), // 0x800000\n    .barbie_pink      = ui_color_rgb(224,  33, 138), // 0xE0218A\n    .steel_pink       = ui_color_rgb(204,  51, 204), // 0xCC33CC\n    .salmon_pink      = ui_color_rgb(255, 145, 164), // 0xFF91A4\n    .gainsboro        = ui_color_rgb(220, 220, 220), // 0xDCDCDC\n    .light_gray       = ui_color_rgb(211, 211, 211), // 0xD3D3D3\n    .silver           = ui_color_rgb(192, 192, 192), // 0xC0C0C0\n    .dark_gray        = ui_color_rgb(169, 169, 169), // 0xA9A9A9\n    .dim_gray         = ui_color_rgb(105, 105, 105), // 0x696969\n    .light_slate_gray = ui_color_rgb(119, 136, 153), // 0x778899\n    .slate_gray       = ui_color_rgb(112, 128, 144), // 0x708090\n    /* Main Panel Backgrounds */\n    .ennui_black                = ui_color_rgb( 18,  18,  18), // 0x1212121\n    .charcoal                   = ui_color_rgb( 54,  69,  79), // 0x36454F\n    .onyx                       = ui_color_rgb( 53,  56,  57), // 0x353839\n    .gunmetal                   = ui_color_rgb( 42,  52,  57), // 0x2A3439\n    .jet_black                  = ui_color_rgb( 52,  52,  52), // 0x343434\n    .outer_space                = ui_color_rgb( 65,  74,  76), // 0x414A4C\n    .eerie_black                = ui_color_rgb( 27,  27,  27), // 0x1B1B1B\n    .oil                        = ui_color_rgb( 59,  60,  54), // 0x3B3C36\n    .black_coral                = ui_color_rgb( 84,  98, 111), // 0x54626F\n    .obsidian                   = ui_color_rgb( 58,  50,  45), // 0x3A322D\n    /* Secondary Panels or Sidebars */\n    .raisin_black               = ui_color_rgb( 39,  38,  53), // 0x272635\n    .dark_charcoal              = ui_color_rgb( 48,  48,  48), // 0x303030\n    .dark_jungle_green          = ui_color_rgb( 26,  36,  33), // 0x1A2421\n    .pine_tree                  = ui_color_rgb( 42,  47,  35), // 0x2A2F23\n    .rich_black                 = ui_color_rgb(  0,  64,  64), // 0x004040\n    .eclipse                    = ui_color_rgb( 63,  57,  57), // 0x3F3939\n    .cafe_noir                  = ui_color_rgb( 75,  54,  33), // 0x4B3621\n\n    /* Flat Buttons */\n    .prussian_blue              = ui_color_rgb(  0,  49,  83), // 0x003153\n    .midnight_green             = ui_color_rgb(  0,  73,  83), // 0x004953\n    .charleston_green           = ui_color_rgb( 35,  43,  43), // 0x232B2B\n    .rich_black_fogra           = ui_color_rgb( 10,  15,  13), // 0x0A0F0D\n    .dark_liver                 = ui_color_rgb( 83,  75,  79), // 0x534B4F\n    .dark_slate_gray            = ui_color_rgb( 47,  79,  79), // 0x2F4F4F\n    .black_olive                = ui_color_rgb( 59,  60,  54), // 0x3B3C36\n    .cadet                      = ui_color_rgb( 83, 104, 114), // 0x536872\n\n    /* Button highlights (hover) */\n    .dark_sienna                = ui_color_rgb( 60,  20,  20), // 0x3C1414\n    .bistre_brown               = ui_color_rgb(150, 113,  23), // 0x967117\n    .dark_puce                  = ui_color_rgb( 79,  58,  60), // 0x4F3A3C\n    .wenge                      = ui_color_rgb(100,  84,  82), // 0x645452\n\n    /* Raised button effects */\n    .dark_scarlet               = ui_color_rgb( 86,   3,  25), // 0x560319\n    .burnt_umber                = ui_color_rgb(138,  51,  36), // 0x8A3324\n    .caput_mortuum              = ui_color_rgb( 89,  39,  32), // 0x592720\n    .barn_red                   = ui_color_rgb(124,  10,   2), // 0x7C0A02\n\n    /* Text and Icons */\n    .platinum                   = ui_color_rgb(229, 228, 226), // 0xE5E4E2\n    .anti_flash_white           = ui_color_rgb(242, 243, 244), // 0xF2F3F4\n    .silver_sand                = ui_color_rgb(191, 193, 194), // 0xBFC1C2\n    .quick_silver               = ui_color_rgb(166, 166, 166), // 0xA6A6A6\n\n    /* Links and Selections */\n    .dark_powder_blue           = ui_color_rgb(  0,  51, 153), // 0x003399\n    .sapphire_blue              = ui_color_rgb( 15,  82, 186), // 0x0F52BA\n    .international_klein_blue   = ui_color_rgb(  0,  47, 167), // 0x002FA7\n    .zaffre                     = ui_color_rgb(  0,  20, 168), // 0x0014A8\n\n    /* Additional Colors */\n    .fish_belly                 = ui_color_rgb(232, 241, 212), // 0xE8F1D4\n    .rusty_red                  = ui_color_rgb(218,  44,  67), // 0xDA2C43\n    .falu_red                   = ui_color_rgb(128,  24,  24), // 0x801818\n    .cordovan                   = ui_color_rgb(137,  63,  69), // 0x893F45\n    .dark_raspberry             = ui_color_rgb(135,  38,  87), // 0x872657\n    .deep_magenta               = ui_color_rgb(204,   0, 204), // 0xCC00CC\n    .byzantium                  = ui_color_rgb(112,  41,  99), // 0x702963\n    .amethyst                   = ui_color_rgb(153, 102, 204), // 0x9966CC\n    .wisteria                   = ui_color_rgb(201, 160, 220), // 0xC9A0DC\n    .lavender_purple            = ui_color_rgb(150, 123, 182), // 0x967BB6\n    .opera_mauve                = ui_color_rgb(183, 132, 167), // 0xB784A7\n    .mauve_taupe                = ui_color_rgb(145,  95, 109), // 0x915F6D\n    .rich_lavender              = ui_color_rgb(167, 107, 207), // 0xA76BCF\n    .pansy_purple               = ui_color_rgb(120,  24,  74), // 0x78184A\n    .violet_eggplant            = ui_color_rgb(153,  17, 153), // 0x991199\n    .jazzberry_jam              = ui_color_rgb(165,  11,  94), // 0xA50B5E\n    .dark_orchid                = ui_color_rgb(153,  50, 204), // 0x9932CC\n    .electric_purple            = ui_color_rgb(191,   0, 255), // 0xBF00FF\n    .sky_magenta                = ui_color_rgb(207, 113, 175), // 0xCF71AF\n    .brilliant_rose             = ui_color_rgb(230, 103, 206), // 0xE667CE\n    .fuchsia_purple             = ui_color_rgb(204,  57, 123), // 0xCC397B\n    .french_raspberry           = ui_color_rgb(199,  44,  72), // 0xC72C48\n    .wild_watermelon            = ui_color_rgb(252, 108, 133), // 0xFC6C85\n    .neon_carrot                = ui_color_rgb(255, 163,  67), // 0xFFA343\n    .burnt_orange               = ui_color_rgb(204,  85,   0), // 0xCC5500\n    .carrot_orange              = ui_color_rgb(237, 145,  33), // 0xED9121\n    .tiger_orange               = ui_color_rgb(253, 106,   2), // 0xFD6A02\n    .giant_onion                = ui_color_rgb(176, 181, 137), // 0xB0B589\n    .rust                       = ui_color_rgb(183,  65,  14), // 0xB7410E\n    .copper_red                 = ui_color_rgb(203, 109,  81), // 0xCB6D51\n    .dark_tangerine             = ui_color_rgb(255, 168,  18), // 0xFFA812\n    .bright_marigold            = ui_color_rgb(252, 192,   6), // 0xFCC006\n    .bone                       = ui_color_rgb(227, 218, 201), // 0xE3DAC9\n\n    /* Earthy Tones */\n    .sienna                     = ui_color_rgb(160,  82,  45), // 0xA0522D\n    .sandy_brown                = ui_color_rgb(244, 164,  96), // 0xF4A460\n    .golden_brown               = ui_color_rgb(153, 101,  21), // 0x996515\n    .camel                      = ui_color_rgb(193, 154, 107), // 0xC19A6B\n    .burnt_sienna               = ui_color_rgb(238, 124,  88), // 0xEE7C58\n    .khaki                      = ui_color_rgb(195, 176, 145), // 0xC3B091\n    .dark_khaki                 = ui_color_rgb(189, 183, 107), // 0xBDB76B\n\n    /* Greens */\n    .fern_green                 = ui_color_rgb( 79, 121,  66), // 0x4F7942\n    .moss_green                 = ui_color_rgb(138, 154,  91), // 0x8A9A5B\n    .myrtle_green               = ui_color_rgb( 49, 120, 115), // 0x317873\n    .pine_green                 = ui_color_rgb(  1, 121, 111), // 0x01796F\n    .jungle_green               = ui_color_rgb( 41, 171, 135), // 0x29AB87\n    .sacramento_green           = ui_color_rgb(  4,  57,  39), // 0x043927\n\n    /* Blues */\n    .yale_blue                  = ui_color_rgb( 15,  77, 146), // 0x0F4D92\n    .cobalt_blue                = ui_color_rgb(  0,  71, 171), // 0x0047AB\n    .persian_blue               = ui_color_rgb( 28,  57, 187), // 0x1C39BB\n    .royal_blue                 = ui_color_rgb( 65, 105, 225), // 0x4169E1\n    .iceberg                    = ui_color_rgb(113, 166, 210), // 0x71A6D2\n    .blue_yonder                = ui_color_rgb( 80, 114, 167), // 0x5072A7\n\n    /* Miscellaneous */\n    .cocoa_brown                = ui_color_rgb(210, 105,  30), // 0xD2691E\n    .cinnamon_satin             = ui_color_rgb(205,  96, 126), // 0xCD607E\n    .fallow                     = ui_color_rgb(193, 154, 107), // 0xC19A6B\n    .cafe_au_lait               = ui_color_rgb(166, 123,  91), // 0xA67B5B\n    .liver                      = ui_color_rgb(103,  76,  71), // 0x674C47\n    .shadow                     = ui_color_rgb(138, 121,  93), // 0x8A795D\n    .cool_grey                  = ui_color_rgb(140, 146, 172), // 0x8C92AC\n    .payne_grey                 = ui_color_rgb( 83, 104, 120), // 0x536878\n\n    /* Lighter Tones for Contrast */\n    .timberwolf                 = ui_color_rgb(219, 215, 210), // 0xDBD7D2\n    .silver_chalice             = ui_color_rgb(172, 172, 172), // 0xACACAC\n    .roman_silver               = ui_color_rgb(131, 137, 150), // 0x838996\n\n    /* Dark Mode Specific Highlights */\n    .electric_lavender          = ui_color_rgb(244, 191, 255), // 0xF4BFFF\n    .magenta_haze               = ui_color_rgb(159,  69, 118), // 0x9F4576\n    .cyber_grape                = ui_color_rgb( 88,  66, 124), // 0x58427C\n    .purple_navy                = ui_color_rgb( 78,  81, 128), // 0x4E5180\n    .liberty                    = ui_color_rgb( 84,  90, 167), // 0x545AA7\n    .purple_mountain_majesty    = ui_color_rgb(150, 120, 182), // 0x9678B6\n    .ceil                       = ui_color_rgb(146, 161, 207), // 0x92A1CF\n    .moonstone_blue             = ui_color_rgb(115, 169, 194), // 0x73A9C2\n    .independence               = ui_color_rgb( 76,  81, 109)  // 0x4C516D\n};\n\n"
  },
  {
    "path": "src/ui/ui_containers.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic bool ui_containers_debug;\n\n#pragma push_macro(\"debugln\")\n#pragma push_macro(\"ui_layout_dump\")\n#pragma push_macro(\"ui_layout_enter\")\n#pragma push_macro(\"ui_layout_exit\")\n\n// Usage of: ui_view_for_each_begin(p, c) { ... } ui_view_for_each_end(p, c)\n// makes code inside iterator debugger friendly and ensures correct __LINE__\n\n#define debugln(...) do {                                   \\\n    if (ui_containers_debug) {  rt_println(__VA_ARGS__); }  \\\n} while (0)\n\nstatic int32_t ui_layout_nesting;\n\n#define ui_layout_enter(v) do {                                         \\\n    ui_ltrb_t i_ = ui_view.margins(v, &v->insets);                      \\\n    ui_ltrb_t p_ = ui_view.margins(v, &v->padding);                     \\\n    debugln(\"%*c> %4d,%-4d %4dx%-4d p: %d %d %d %d i: %d %d %d %d %s\",  \\\n            ui_layout_nesting, 0x20,                                    \\\n            v->x, v->y, v->w, v->h,                                     \\\n            p_.left, p_.top, p_.right, p_.bottom,                       \\\n            i_.left, i_.top, i_.right, i_.bottom,                       \\\n            ui_view_debug_id(v));                                       \\\n    ui_layout_nesting += 4;                                             \\\n} while (0)\n\n#define ui_layout_exit(v) do {                                          \\\n    ui_layout_nesting -= 4;                                             \\\n    debugln(\"%*c< %4d,%-4d %4dx%-4d %s\",                                \\\n            ui_layout_nesting, 0x20,                                    \\\n            v->x, v->y, v->w, v->h, ui_view_debug_id(v));               \\\n} while (0)\n\n#define ui_layout_clild(v) do {                                         \\\n    debugln(\"%*c %4d,%-4d %4dx%-4d %s\", ui_layout_nesting, 0x20,        \\\n            c->x, c->y, c->w, c->h, ui_view_debug_id(v));               \\\n} while (0)\n\nstatic const char* ui_stack_finite_int(int32_t v, char* text, int32_t count) {\n    rt_swear(v >= 0);\n    if (v == ui.infinity) {\n        rt_str.format(text, count, \"%s\", rt_glyph_infinity);\n    } else {\n        rt_str.format(text, count, \"%d\", v);\n    }\n    return text;\n}\n\n#define ui_layout_dump(v) do {                                                \\\n    char maxw[32];                                                            \\\n    char maxh[32];                                                            \\\n    debugln(\"%s[%4.4s] %4d,%-4d %4dx%-4d, max[%sx%s] \"                        \\\n        \"padding { %.3f %.3f %.3f %.3f } \"                                    \\\n        \"insets { %.3f %.3f %.3f %.3f } align: 0x%02X\",                       \\\n        ui_view_debug_id(v),                                                  \\\n        &v->type, v->x, v->y, v->w, v->h,                                     \\\n        ui_stack_finite_int(v->max_w, maxw, rt_countof(maxw)),                \\\n        ui_stack_finite_int(v->max_h, maxh, rt_countof(maxh)),                \\\n        v->padding.left, v->padding.top, v->padding.right, v->padding.bottom, \\\n        v->insets.left, v->insets.top, v->insets.right, v->insets.bottom,     \\\n        v->align);                                                            \\\n} while (0)\n\nstatic void ui_span_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_span, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_ltrb_t insets;\n    ui_view.inbox(p, null, &insets);\n    int32_t w = insets.left;\n    int32_t h = 0;\n    int32_t max_w = w;\n    ui_view_for_each_begin(p, c) {\n        rt_swear(c->max_w == 0 || c->max_w >= c->w,\n              \"max_w: %d w: %d\", c->max_w, c->w);\n        if (ui_view.is_hidden(c)) {\n            // nothing\n        } else if (c->type == ui_view_spacer) {\n            c->padding = (ui_margins_t){ 0, 0, 0, 0 };\n            c->w = 0; // layout will distribute excess here\n            c->h = 0; // starts with zero\n            max_w = ui.infinity; // spacer make width greedy\n        } else {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            h = rt_max(h, cbx.h);\n            if (c->max_w == ui.infinity) {\n                max_w = ui.infinity;\n            } else if (max_w < ui.infinity && c->max_w != 0) {\n                rt_swear(c->max_w >= c->w, \"c->max_w %d < c->w %d \",\n                      c->max_w, c->w);\n                max_w += c->max_w;\n            } else if (max_w < ui.infinity) {\n                rt_swear(0 <= max_w + cbx.w &&\n                      (int64_t)max_w + (int64_t)cbx.w < (int64_t)ui.infinity,\n                      \"max_w:%d + cbx.w:%d = %d\", max_w, cbx.w, max_w + cbx.w);\n                max_w += cbx.w;\n            }\n            w += cbx.w;\n        }\n        ui_layout_clild(c);\n    } ui_view_for_each_end(p, c);\n    if (0 < max_w && max_w < ui.infinity) {\n        rt_swear(0 <= max_w + insets.right &&\n              (int64_t)max_w + (int64_t)insets.right < (int64_t)ui.infinity,\n             \"max_w:%d + right:%d = %d\", max_w, insets.right, max_w + insets.right);\n        max_w += insets.right;\n    }\n    rt_swear(max_w == 0 || max_w >= w, \"max_w: %d w: %d\", max_w, w);\n    if (ui_view.is_hidden(p)) {\n        p->w = 0;\n        p->h = 0;\n    } else {\n        p->w = w + insets.right;\n        p->h = insets.top + h + insets.bottom;\n        rt_swear(p->max_w == 0 || p->max_w >= p->w,\n              \"max_w: %d is less than actual width: %d\", p->max_w, p->w);\n    }\n    ui_layout_exit(p);\n}\n\n// after measure of the subtree is concluded the parent ui_span\n// may adjust span_w wider number depending on it's own width\n// and ui_span.max_w agreement\n\nstatic int32_t ui_span_place_child(ui_view_t* c, ui_rect_t pbx, int32_t x) {\n    ui_ltrb_t padding = ui_view.margins(c, &c->padding);\n    // setting child`s max_h to infinity means that child`s height is\n    // *always* fill vertical view size of the parent\n    // childs.h can exceed parent.h (vertical overflow) - is not\n    // encouraged but allowed\n    if (c->max_h == ui.infinity) {\n        // important c->h changed, cbx.h is no longer valid\n        c->h = rt_max(c->h, pbx.h - padding.top - padding.bottom);\n    }\n    int32_t min_y = pbx.y + padding.top;\n    if ((c->align & ui.align.top) != 0) {\n        rt_assert(c->align == ui.align.top);\n        c->y = min_y;\n    } else if ((c->align & ui.align.bottom) != 0) {\n        rt_assert(c->align == ui.align.bottom);\n        c->y = rt_max(min_y, pbx.y + pbx.h - c->h - padding.bottom);\n    } else { // effective height (c->h might have been changed)\n        rt_assert(c->align == ui.align.center,\n                  \"only top, center, bottom alignment for span\");\n        const int32_t ch = padding.top + c->h + padding.bottom;\n        c->y = rt_max(min_y, pbx.y + (pbx.h - ch) / 2 + padding.top);\n    }\n    c->x = x + padding.left;\n    return c->x + c->w + padding.right;\n}\n\nstatic void ui_span_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_span, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t spacers = 0; // Number of spacers\n    int32_t max_w_count = 0;\n    int32_t x = p->x + insets.left;\n    ui_view_for_each_begin(p, c) {\n        if (!ui_view.is_hidden(c)) {\n            if (c->type == ui_view_spacer) {\n                c->x = x;\n                c->y = pbx.y;\n                c->h = pbx.h;\n                c->w = 0;\n                spacers++;\n            } else {\n                x = ui_span_place_child(c, pbx, x);\n                rt_swear(c->max_w == 0 || c->max_w >= c->w,\n                      \"max_w:%d < w:%d\", c->max_w, c->w);\n                if (c->max_w > 0) {\n                    max_w_count++;\n                }\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    int32_t xw = rt_max(0, pbx.x + pbx.w - x); // excess width\n    int32_t max_w_sum = 0;\n    if (xw > 0 && max_w_count > 0) {\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c) && c->type != ui_view_spacer &&\n                 c->max_w > 0) {\n                max_w_sum += rt_min(c->max_w, xw);\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    if (xw > 0 && max_w_count > 0) {\n        debugln(\"%*c pass 2: fill parent\", ui_layout_nesting, 0x20);\n        x = p->x + insets.left;\n        int32_t k = 0;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    rt_swear(padding.left == 0 && padding.right == 0);\n                } else if (c->max_w > 0) {\n                    const int32_t max_w = rt_min(c->max_w, xw);\n                    int64_t proportional = (xw * (int64_t)max_w) / max_w_sum;\n                    rt_assert(proportional <= (int64_t)INT32_MAX);\n                    int32_t cw = (int32_t)proportional;\n                    c->w = rt_min(c->max_w, c->w + cw);\n                    k++;\n                }\n                // TODO: take into account .align of a child and adjust x\n                //       depending on ui.align.left/right/center\n                //       distributing excess width on the left and right of a child\n                c->x = padding.left + x;\n                x = c->x + padding.left + c->w + padding.right;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n        rt_swear(k == max_w_count);\n    }\n    // excess width after max_w of non-spacers taken into account\n    xw = rt_max(0, pbx.x + pbx.w - x);\n    if (xw > 0 && spacers > 0) {\n        // evenly distribute excess among spacers\n        debugln(\"%*c pass 3: expand spacers\", ui_layout_nesting, 0x20);\n        int32_t partial = xw / spacers;\n        x = p->x + insets.left;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    c->y = pbx.y;\n                    c->w = partial;\n                    c->h = pbx.h;\n                    spacers--;\n                }\n                c->x = x + padding.left;\n                x = c->x + c->w + padding.right;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    ui_layout_exit(p);\n}\n\nstatic void ui_list_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_list, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t max_h = insets.top;\n    int32_t h = insets.top;\n    int32_t w = 0;\n    ui_view_for_each_begin(p, c) {\n        rt_swear(c->max_h == 0 || c->max_h >= c->h, \"max_h: %d h: %d\",\n              c->max_h, c->h);\n        if (!ui_view.is_hidden(c)) {\n            if (c->type == ui_view_spacer) {\n                c->padding = (ui_margins_t){ 0, 0, 0, 0 };\n                c->h = 0; // layout will distribute excess here\n                max_h = ui.infinity; // spacer make height greedy\n            } else {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                w = rt_max(w, cbx.w);\n                if (c->max_h == ui.infinity) {\n                    max_h = ui.infinity;\n                } else if (max_h < ui.infinity && c->max_h != 0) {\n                    rt_swear(c->max_h >= c->h, \"c->max_h:%d < c->h: %d\",\n                          c->max_h, c->h);\n                    max_h += c->max_h;\n                } else if (max_h < ui.infinity) {\n                    rt_swear(0 <= max_h + cbx.h &&\n                          (int64_t)max_h + (int64_t)cbx.h < (int64_t)ui.infinity,\n                          \"max_h:%d + ch:%d = %d\", max_h, cbx.h, max_h + cbx.h);\n                    max_h += cbx.h;\n                }\n                h += cbx.h;\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    if (max_h < ui.infinity) {\n        rt_swear(0 <= max_h + insets.bottom &&\n              (int64_t)max_h + (int64_t)insets.bottom < (int64_t)ui.infinity,\n             \"max_h:%d + bottom:%d = %d\",\n              max_h, insets.bottom, max_h + insets.bottom);\n        max_h += insets.bottom;\n    }\n    if (ui_view.is_hidden(p)) {\n        p->w = 0;\n        p->h = 0;\n    } else if (p == ui_app.root) {\n        // ui_app.root is special occupying whole window client rectangle\n        // sans borders and caption thus it should not be re-measured\n    } else {\n        p->h = h + insets.bottom;\n        p->w = insets.left + w + insets.right;\n    }\n    ui_layout_exit(p);\n}\n\nstatic int32_t ui_list_place_child(ui_view_t* c, ui_rect_t pbx, int32_t y) {\n    ui_ltrb_t padding = ui_view.margins(c, &c->padding);\n    // setting child`s max_w to infinity means that child`s height is\n    // *always* fill vertical view size of the parent\n    // childs.w can exceed parent.w (horizontal overflow) - not encouraged but allowed\n    if (c->max_w == ui.infinity) {\n        c->w = rt_max(c->w, pbx.w - padding.left - padding.right);\n    }\n    int32_t min_x = pbx.x + padding.left;\n    if ((c->align & ui.align.left) != 0) {\n        rt_assert(c->align == ui.align.left);\n        c->x = min_x;\n    } else if ((c->align & ui.align.right) != 0) {\n        rt_assert(c->align == ui.align.right);\n        c->x = rt_max(min_x, pbx.x + pbx.w - c->w - padding.right);\n    } else {\n        rt_assert(c->align == ui.align.center,\n                  \"only left, center, right, alignment for list\");\n        const int32_t cw = padding.left + c->w + padding.right;\n        c->x = rt_max(min_x, pbx.x + (pbx.w - cw) / 2 + padding.left);\n    }\n    c->y = y + padding.top;\n    return c->y + c->h + padding.bottom;\n}\n\nstatic void ui_list_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_list, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    int32_t spacers = 0; // Number of spacers\n    int32_t max_h_sum = 0;\n    int32_t max_h_count = 0;\n    int32_t y = pbx.y;\n    ui_view_for_each_begin(p, c) {\n        if (ui_view.is_hidden(c)) {\n            // nothing\n        } else if (c->type == ui_view_spacer) {\n            c->x = pbx.x;\n            c->y = y;\n            c->w = pbx.w;\n            c->h = 0;\n            spacers++;\n        } else {\n            y = ui_list_place_child(c, pbx, y);\n            rt_swear(c->max_h == 0 || c->max_h >= c->h,\n                  \"max_h:%d < h:%d\", c->max_h, c->h);\n            if (c->max_h > 0) {\n                // clamp max_h to the effective parent height\n                max_h_count++;\n            }\n        }\n    } ui_view_for_each_end(p, c);\n    int32_t xh = rt_max(0, pbx.y + pbx.h - y); // excess height\n    if (xh > 0 && max_h_count > 0) {\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c) && c->type != ui_view_spacer &&\n                 c->max_h > 0) {\n                max_h_sum += rt_min(c->max_h, xh);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    if (xh > 0 && max_h_count > 0) {\n        debugln(\"%*c pass 2: fill parent\", ui_layout_nesting, 0x20);\n        y = pbx.y;\n        int32_t k = 0;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type != ui_view_spacer && c->max_h > 0) {\n                    const int32_t max_h = rt_min(c->max_h, xh);\n                    int64_t proportional = (xh * (int64_t)max_h) / max_h_sum;\n                    rt_assert(proportional <= (int64_t)INT32_MAX);\n                    int32_t ch = (int32_t)proportional;\n                    c->h = rt_min(c->max_h, c->h + ch);\n                    k++;\n                }\n                int32_t ch = padding.top + c->h + padding.bottom;\n                c->y = y + padding.top;\n                y += ch;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n        rt_swear(k == max_h_count);\n    }\n    // excess height after max_h of non-spacers taken into account\n    xh = rt_max(0, pbx.y + pbx.h - y); // excess height\n    if (xh > 0 && spacers > 0) {\n        // evenly distribute excess among spacers\n        debugln(\"%*c pass 3: expand spacers\", ui_layout_nesting, 0x20);\n        int32_t partial = xh / spacers;\n        y = pbx.y;\n        ui_view_for_each_begin(p, c) {\n            if (!ui_view.is_hidden(c)) {\n                ui_rect_t cbx; // child \"out\" box expanded by padding\n                ui_ltrb_t padding;\n                ui_view.outbox(c, &cbx, &padding);\n                if (c->type == ui_view_spacer) {\n                    c->x = pbx.x;\n                    c->w = pbx.x + pbx.w - pbx.x;\n                    c->h = partial; // TODO: last?\n                    spacers--;\n                }\n                int32_t ch = padding.top + c->h + padding.bottom;\n                c->y = y + padding.top;\n                y += ch;\n                ui_layout_clild(c);\n            }\n        } ui_view_for_each_end(p, c);\n    }\n    ui_layout_exit(p);\n}\n\nstatic void ui_stack_child_3x3(ui_view_t* c, int32_t *row, int32_t *col) {\n    *row = 0; *col = 0; // makes code analysis happier\n    if (c->align == (ui.align.left|ui.align.top)) {\n        *row = 0; *col = 0;\n    } else if (c->align == ui.align.top) {\n        *row = 0; *col = 1;\n    } else if (c->align == (ui.align.right|ui.align.top)) {\n        *row = 0; *col = 2;\n    } else if (c->align == ui.align.left) {\n        *row = 1; *col = 0;\n    } else if (c->align == ui.align.center) {\n        *row = 1; *col = 1;\n    } else if (c->align == ui.align.right) {\n        *row = 1; *col = 2;\n    } else if (c->align == (ui.align.left|ui.align.bottom)) {\n        *row = 2; *col = 0;\n    } else if (c->align == ui.align.bottom) {\n        *row = 2; *col = 1;\n    } else if (c->align == (ui.align.right|ui.align.bottom)) {\n        *row = 2; *col = 2;\n    } else {\n        rt_swear(false, \"invalid child align: 0x%02X\", c->align);\n    }\n}\n\nstatic void ui_stack_measure(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_stack, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    ui_wh_t sides[3][3] = { {0, 0} };\n    ui_view_for_each_begin(p, c) {\n        if (!ui_view.is_hidden(c)) {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            int32_t row = 0;\n            int32_t col = 0;\n            ui_stack_child_3x3(c, &row, &col);\n            sides[row][col].w = rt_max(sides[row][col].w, cbx.w);\n            sides[row][col].h = rt_max(sides[row][col].h, cbx.h);\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    if (ui_containers_debug) {\n        for (int32_t r = 0; r < rt_countof(sides); r++) {\n            char text[1024];\n            text[0] = 0;\n            for (int32_t c = 0; c < rt_countof(sides[r]); c++) {\n                char line[128];\n                rt_str_printf(line, \" %4dx%-4d\", sides[r][c].w, sides[r][c].h);\n                strcat(text, line);\n            }\n            debugln(\"%*c sides[%d] %s\", ui_layout_nesting, 0x20, r, text);\n        }\n    }\n    ui_wh_t wh = {0, 0};\n    for (int32_t r = 0; r < 3; r++) {\n        int32_t sum_w = 0;\n        for (int32_t c = 0; c < 3; c++) {\n            sum_w += sides[r][c].w;\n        }\n        wh.w = rt_max(wh.w, sum_w);\n    }\n    for (int32_t c = 0; c < 3; c++) {\n        int32_t sum_h = 0;\n        for (int32_t r = 0; r < 3; r++) {\n            sum_h += sides[r][c].h;\n        }\n        wh.h = rt_max(wh.h, sum_h);\n    }\n    debugln(\"%*c wh %4dx%-4d\", ui_layout_nesting, 0x20, wh.w, wh.h);\n    p->w = insets.left + wh.w + insets.right;\n    p->h = insets.top  + wh.h + insets.bottom;\n    ui_layout_exit(p);\n}\n\nstatic void ui_stack_layout(ui_view_t* p) {\n    ui_layout_enter(p);\n    rt_swear(p->type == ui_view_stack, \"type %4.4s 0x%08X\", &p->type, p->type);\n    ui_rect_t pbx; // parent \"in\" box (sans insets)\n    ui_ltrb_t insets;\n    ui_view.inbox(p, &pbx, &insets);\n    ui_view_for_each_begin(p, c) {\n        if (c->type != ui_view_spacer && !ui_view.is_hidden(c)) {\n            ui_rect_t cbx; // child \"out\" box expanded by padding\n            ui_ltrb_t padding;\n            ui_view.outbox(c, &cbx, &padding);\n            const int32_t pw = p->w - insets.left - insets.right - padding.left - padding.right;\n            const int32_t ph = p->h - insets.top - insets.bottom - padding.top - padding.bottom;\n            int32_t cw = c->max_w == ui.infinity ? pw : c->max_w;\n            if (cw > 0) {\n                c->w = rt_min(cw, pw);\n            }\n            int32_t ch = c->max_h == ui.infinity ? ph : c->max_h;\n            if (ch > 0) {\n                c->h = rt_min(ch, ph);\n            }\n            rt_swear((c->align & (ui.align.left|ui.align.right)) !=\n                               (ui.align.left|ui.align.right),\n                   \"align: left|right 0x%02X\", c->align);\n            rt_swear((c->align & (ui.align.top|ui.align.bottom)) !=\n                               (ui.align.top|ui.align.bottom),\n                   \"align: top|bottom 0x%02X\", c->align);\n            int32_t min_x = pbx.x + padding.left;\n            if ((c->align & ui.align.left) != 0) {\n                c->x = min_x;\n            } else if ((c->align & ui.align.right) != 0) {\n                c->x = rt_max(min_x, pbx.x + pbx.w - c->w - padding.right);\n            } else {\n                c->x = rt_max(min_x, min_x + (pbx.w - (padding.left + c->w + padding.right)) / 2);\n            }\n            int32_t min_y = pbx.y + padding.top;\n            if ((c->align & ui.align.top) != 0) {\n                c->y = min_y;\n            } else if ((c->align & ui.align.bottom) != 0) {\n                c->y = rt_max(min_y, pbx.y + pbx.h - c->h - padding.bottom);\n            } else {\n                c->y = rt_max(min_y, min_y + (pbx.h - (padding.top + c->h + padding.bottom)) / 2);\n            }\n            ui_layout_clild(c);\n        }\n    } ui_view_for_each_end(p, c);\n    ui_layout_exit(p);\n}\n\nstatic void ui_container_paint(ui_view_t* v) {\n    if (!ui_color_is_undefined(v->background) &&\n        !ui_color_is_transparent(v->background)) {\n        ui_gdi.fill(v->x, v->y, v->w, v->h, v->background);\n    } else {\n//      rt_println(\"%s undefined\", ui_view_debug_id(v));\n    }\n}\n\nstatic void ui_view_container_init(ui_view_t* v) {\n    v->background = ui_colors.transparent;\n    v->insets  = (ui_margins_t){\n       .left  = 0.25, .top    = 0.125,\n        .right = 0.25, .bottom = 0.125\n//      .left  = 0.25, .top    = 0.0625,  // TODO: why?\n//      .right = 0.25, .bottom = 0.1875\n    };\n}\n\nvoid ui_view_init_span(ui_view_t* v) {\n    rt_swear(v->type == ui_view_span, \"type %4.4s 0x%08X\", &v->type, v->type);\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_span_measure; }\n    if (v->layout  == null) { v->layout  = ui_span_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_span\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_span\"; }\n}\n\nvoid ui_view_init_list(ui_view_t* v) {\n    rt_swear(v->type == ui_view_list, \"type %4.4s 0x%08X\", &v->type, v->type);\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_list_measure; }\n    if (v->layout  == null) { v->layout  = ui_list_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_list\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_list\"; }\n}\n\nvoid ui_view_init_spacer(ui_view_t* v) {\n    rt_swear(v->type == ui_view_spacer, \"type %4.4s 0x%08X\", &v->type, v->type);\n    v->w = 0;\n    v->h = 0;\n    v->max_w = ui.infinity;\n    v->max_h = ui.infinity;\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_spacer\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_spacer\"; }\n\n}\n\nvoid ui_view_init_stack(ui_view_t* v) {\n    ui_view_container_init(v);\n    if (v->measure == null) { v->measure = ui_stack_measure; }\n    if (v->layout  == null) { v->layout  = ui_stack_layout; }\n    if (v->paint   == null) { v->paint   = ui_container_paint; }\n    if (ui_view.string(v)[0] == 0) { ui_view.set_text(v, \"ui_stack\"); }\n    if (v->debug.id == null) { v->debug.id = \"#ui_stack\"; }\n}\n\n#pragma pop_macro(\"ui_layout_exit\")\n#pragma pop_macro(\"ui_layout_enter\")\n#pragma pop_macro(\"ui_layout_dump\")\n#pragma pop_macro(\"debugln\")\n"
  },
  {
    "path": "src/ui/ui_core.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"rt/rt_win32.h\"\n\n#define UI_WM_ANIMATE  (WM_APP + 0x7FFF)\n#define UI_WM_OPENING  (WM_APP + 0x7FFE)\n#define UI_WM_CLOSING  (WM_APP + 0x7FFD)\n#define UI_WM_TAP      (WM_APP + 0x7FFC)\n#define UI_WM_DTAP     (WM_APP + 0x7FFB) // double tap (aka click)\n#define UI_WM_PRESS    (WM_APP + 0x7FFA)\n\nstatic bool ui_point_in_rect(const ui_point_t* p, const ui_rect_t* r) {\n    return r->x <= p->x && p->x < r->x + r->w &&\n           r->y <= p->y && p->y < r->y + r->h;\n}\n\nstatic bool ui_intersect_rect(ui_rect_t* i, const ui_rect_t* r0,\n                                            const ui_rect_t* r1) {\n    ui_rect_t r = {0};\n    r.x = rt_max(r0->x, r1->x);  // Maximum of left edges\n    r.y = rt_max(r0->y, r1->y);  // Maximum of top edges\n    r.w = rt_min(r0->x + r0->w, r1->x + r1->w) - r.x;  // Width of overlap\n    r.h = rt_min(r0->y + r0->h, r1->y + r1->h) - r.y;  // Height of overlap\n    bool b = r.w > 0 && r.h > 0;\n    if (!b) {\n        r.w = 0;\n        r.h = 0;\n    }\n    if (i != null) { *i = r; }\n    return b;\n}\n\nstatic ui_rect_t ui_combine_rect(const ui_rect_t* r0, const ui_rect_t* r1) {\n    return (ui_rect_t) {\n        .x = rt_min(r0->x, r1->x),\n        .y = rt_min(r0->y, r1->y),\n        .w = rt_max(r0->x + r0->w, r1->x + r1->w) - rt_min(r0->x, r1->x),\n        .h = rt_max(r0->y + r0->h, r1->y + r1->h) - rt_min(r0->y, r1->y)\n    };\n}\n\nui_if ui = {\n    .point_in_rect  = ui_point_in_rect,\n    .intersect_rect = ui_intersect_rect,\n    .combine_rect   = ui_combine_rect,\n    .infinity = INT32_MAX,\n    .align = {\n        .center = 0,\n        .left   = 0x01,\n        .top    = 0x02,\n        .right  = 0x10,\n        .bottom = 0x20\n    },\n    .visibility = { // window visibility see ShowWindow link below\n        .hide      = SW_HIDE,\n        .normal    = SW_SHOWNORMAL,\n        .minimize  = SW_SHOWMINIMIZED,\n        .maximize  = SW_SHOWMAXIMIZED,\n        .normal_na = SW_SHOWNOACTIVATE,\n        .show      = SW_SHOW,\n        .min_next  = SW_MINIMIZE,\n        .min_na    = SW_SHOWMINNOACTIVE,\n        .show_na   = SW_SHOWNA,\n        .restore   = SW_RESTORE,\n        .defau1t   = SW_SHOWDEFAULT,\n        .force_min = SW_FORCEMINIMIZE\n    },\n    .message = {\n        .animate               = UI_WM_ANIMATE,\n        .opening               = UI_WM_OPENING,\n        .closing               = UI_WM_CLOSING\n    },\n    .mouse = {\n        .button = {\n            .left  = MK_LBUTTON,\n            .right = MK_RBUTTON\n        }\n    },\n    .hit_test = {\n        .error             = HTERROR,\n        .transparent       = HTTRANSPARENT,\n        .nowhere           = HTNOWHERE,\n        .client            = HTCLIENT,\n        .caption           = HTCAPTION,\n        .system_menu       = HTSYSMENU,\n        .grow_box          = HTGROWBOX,\n        .menu              = HTMENU,\n        .horizontal_scroll = HTHSCROLL,\n        .vertical_scroll   = HTVSCROLL,\n        .min_button        = HTMINBUTTON,\n        .max_button        = HTMAXBUTTON,\n        .left              = HTLEFT,\n        .right             = HTRIGHT,\n        .top               = HTTOP,\n        .top_left          = HTTOPLEFT,\n        .top_right         = HTTOPRIGHT,\n        .bottom            = HTBOTTOM,\n        .bottom_left       = HTBOTTOMLEFT,\n        .bottom_right      = HTBOTTOMRIGHT,\n        .border            = HTBORDER,\n        .object            = HTOBJECT,\n        .close             = HTCLOSE,\n        .help              = HTHELP\n    },\n    .key = {\n        .up        = VK_UP,\n        .down      = VK_DOWN,\n        .left      = VK_LEFT,\n        .right     = VK_RIGHT,\n        .home      = VK_HOME,\n        .end       = VK_END,\n        .page_up   = VK_PRIOR,\n        .page_down = VK_NEXT,\n        .insert    = VK_INSERT,\n        .del       = VK_DELETE,\n        .back      = VK_BACK,\n        .escape    = VK_ESCAPE,\n        .enter     = VK_RETURN,\n        .minus     = VK_OEM_MINUS,\n        .plus      = VK_OEM_PLUS,\n        .f1        = VK_F1,\n        .f2        = VK_F2,\n        .f3        = VK_F3,\n        .f4        = VK_F4,\n        .f5        = VK_F5,\n        .f6        = VK_F6,\n        .f7        = VK_F7,\n        .f8        = VK_F8,\n        .f9        = VK_F9,\n        .f10       = VK_F10,\n        .f11       = VK_F11,\n        .f12       = VK_F12,\n        .f13       = VK_F13,\n        .f14       = VK_F14,\n        .f15       = VK_F15,\n        .f16       = VK_F16,\n        .f17       = VK_F17,\n        .f18       = VK_F18,\n        .f19       = VK_F19,\n        .f20       = VK_F20,\n        .f21       = VK_F21,\n        .f22       = VK_F22,\n        .f23       = VK_F23,\n        .f24       = VK_F24,\n    },\n    .beep = {\n        .ok         = 0,\n        .info       = 1,\n        .question   = 2,\n        .warning    = 3,\n        .error      = 4\n    }\n};\n\n// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow\n"
  },
  {
    "path": "src/ui/ui_edit_doc.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\n#undef UI_EDIT_STR_TEST\n#undef UI_EDIT_DOC_TEST\n#undef UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n#undef UI_EDIT_DOC_TEST_PARAGRAPHS\n\n#if 0 // flip to 1 to run tests\n\n#define UI_EDIT_STR_TEST\n#define UI_EDIT_DOC_TEST\n\n#if 0 // flip to 1 to run exhausting lengthy tests\n#define UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n#define UI_EDIT_DOC_TEST_PARAGRAPHS\n#endif\n\n#endif\n\n#pragma push_macro(\"ui_edit_check_zeros\")\n#pragma push_macro(\"ui_edit_check_pg_inside_text\")\n#pragma push_macro(\"ui_edit_check_range_inside_text\")\n#pragma push_macro(\"ui_edit_pg_dump\")\n#pragma push_macro(\"ui_edit_range_dump\")\n#pragma push_macro(\"ui_edit_text_dump\")\n#pragma push_macro(\"ui_edit_doc_dump\")\n\n#define ui_edit_pg_dump(pg)                              \\\n    rt_debug.println(__FILE__, __LINE__, __func__,       \\\n                    \"pn:%d gp:%d\", (pg)->pn, (pg)->gp)\n\n#define ui_edit_range_dump(r)                            \\\n    rt_debug.println(__FILE__, __LINE__, __func__,       \\\n            \"from {pn:%d gp:%d} to {pn:%d gp:%d}\",       \\\n    (r)->from.pn, (r)->from.gp, (r)->to.pn, (r)->to.gp);\n\n#define ui_edit_text_dump(t) do {                        \\\n    for (int32_t i_ = 0; i_ < (t)->np; i_++) {           \\\n        const ui_edit_str_t* p_ = &t->ps[i_];            \\\n        rt_debug.println(__FILE__, __LINE__, __func__,   \\\n            \"ps[%d].%d: %.*s\", i_, p_->b, p_->b, p_->u); \\\n    }                                                    \\\n} while (0)\n\n// TODO: undo/redo stacks and listeners\n#define ui_edit_doc_dump(d) do {                                \\\n    for (int32_t i_ = 0; i_ < (d)->text.np; i_++) {             \\\n        const ui_edit_str_t* p_ = &(d)->text.ps[i_];            \\\n        rt_debug.println(__FILE__, __LINE__, __func__,          \\\n            \"ps[%d].b:%d.c:%d: %p %.*s\", i_, p_->b, p_->c,      \\\n            p_, p_->b, p_->u);                                  \\\n    }                                                           \\\n} while (0)\n\n\n#ifdef DEBUG\n\n// ui_edit_check_zeros only works for packed structs:\n\n#define ui_edit_check_zeros(a_, b_) do {                                    \\\n    for (int32_t i_ = 0; i_ < (b_); i_++) {                                 \\\n        rt_assert(((const uint8_t*)(a_))[i_] == 0x00);                         \\\n    }                                                                       \\\n} while (0)\n\n#define ui_edit_check_pg_inside_text(t_, pg_)                               \\\n    rt_assert(0 <= (pg_)->pn && (pg_)->pn < (t_)->np &&                        \\\n           0 <= (pg_)->gp && (pg_)->gp <= (t_)->ps[(pg_)->pn].g)\n\n#define ui_edit_check_range_inside_text(t_, r_) do {                        \\\n    rt_assert((r_)->from.pn <= (r_)->to.pn);                                   \\\n    rt_assert((r_)->from.pn <  (r_)->to.pn || (r_)->from.gp <= (r_)->to.gp);   \\\n    ui_edit_check_pg_inside_text(t_, (&(r_)->from));                        \\\n    ui_edit_check_pg_inside_text(t_, (&(r_)->to));                          \\\n} while (0)\n\n#else\n\n#define ui_edit_check_zeros(a, b)             do { } while (0)\n#define ui_edit_check_pg_inside_text(t, pg)   do { } while (0)\n#define ui_edit_check_range_inside_text(t, r) do { } while (0)\n\n#endif\n\nstatic ui_edit_range_t ui_edit_text_all_on_null(const ui_edit_text_t* t,\n        const ui_edit_range_t* range) {\n    ui_edit_range_t r;\n    if (range != null) {\n        r = *range;\n    } else {\n        rt_assert(t->np >= 1);\n        r.from.pn = 0;\n        r.from.gp = 0;\n        r.to.pn = t->np - 1;\n        r.to.gp = t->ps[r.to.pn].g;\n    }\n    return r;\n}\n\nstatic int ui_edit_range_compare(const ui_edit_pg_t pg1, const ui_edit_pg_t pg2) {\n    int64_t d = (((int64_t)pg1.pn << 32) | pg1.gp) -\n                (((int64_t)pg2.pn << 32) | pg2.gp);\n    return d < 0 ? -1 : d > 0 ? 1 : 0;\n}\n\nstatic ui_edit_range_t ui_edit_range_order(const ui_edit_range_t range) {\n    ui_edit_range_t r = range;\n    uint64_t f = ((uint64_t)r.from.pn << 32) | r.from.gp;\n    uint64_t t = ((uint64_t)r.to.pn   << 32) | r.to.gp;\n    if (ui_edit_range.compare(r.from, r.to) > 0) {\n        uint64_t swap = t; t = f; f = swap;\n        r.from.pn = (int32_t)(f >> 32);\n        r.from.gp = (int32_t)(f);\n        r.to.pn   = (int32_t)(t >> 32);\n        r.to.gp   = (int32_t)(t);\n    }\n    return r;\n}\n\nstatic ui_edit_range_t ui_edit_text_ordered(const ui_edit_text_t* t,\n        const ui_edit_range_t* r) {\n    return ui_edit_range.order(ui_edit_text.all_on_null(t, r));\n}\n\nstatic bool ui_edit_range_is_valid(const ui_edit_range_t r) {\n    if (0 <= r.from.pn && 0 <= r.to.pn &&\n        0 <= r.from.gp && 0 <= r.to.gp) {\n        ui_edit_range_t o = ui_edit_range.order(r);\n        return ui_edit_range.compare(o.from, o.to) <= 0;\n    } else {\n        return false;\n    }\n}\n\nstatic bool ui_edit_range_is_empty(const ui_edit_range_t r) {\n    return r.from.pn == r.to.pn && r.from.gp == r.to.gp;\n}\n\nstatic ui_edit_pg_t ui_edit_text_end(const ui_edit_text_t* t) {\n    return (ui_edit_pg_t){ .pn = t->np - 1, .gp = t->ps[t->np - 1].g };\n}\n\nstatic ui_edit_range_t ui_edit_text_end_range(const ui_edit_text_t* t) {\n    ui_edit_pg_t e = (ui_edit_pg_t){ .pn = t->np - 1,\n                                     .gp = t->ps[t->np - 1].g };\n    return (ui_edit_range_t){ .from = e, .to = e };\n}\n\nstatic uint64_t ui_edit_range_uint64(const ui_edit_pg_t pg) {\n    rt_assert(pg.pn >= 0 && pg.gp >= 0);\n    return ((uint64_t)pg.pn << 32) | (uint64_t)pg.gp;\n}\n\nstatic ui_edit_pg_t ui_edit_range_pg(uint64_t uint64) {\n    rt_assert((int32_t)(uint64 >> 32) >= 0 && (int32_t)uint64 >= 0);\n    return (ui_edit_pg_t){ .pn = (int32_t)(uint64 >> 32), .gp = (int32_t)uint64 };\n}\n\nstatic bool ui_edit_range_inside_text(const ui_edit_text_t* t,\n        const ui_edit_range_t r) {\n    return ui_edit_range.is_valid(r) &&\n            0 <= r.from.pn && r.from.pn <= r.to.pn && r.to.pn < t->np &&\n            0 <= r.from.gp && r.from.gp <= r.to.gp &&\n            r.to.gp <= t->ps[r.to.pn - 1].g;\n}\n\nstatic ui_edit_range_t ui_edit_range_intersect(const ui_edit_range_t r1,\n    const ui_edit_range_t r2) {\n    if (ui_edit_range.is_valid(r1) && ui_edit_range.is_valid(r2)) {\n        ui_edit_range_t o1 = ui_edit_range.order(r1);\n        ui_edit_range_t o2 = ui_edit_range.order(r1);\n        uint64_t f1 = ((uint64_t)o1.from.pn << 32) | o1.from.gp;\n        uint64_t t1 = ((uint64_t)o1.to.pn   << 32) | o1.to.gp;\n        uint64_t f2 = ((uint64_t)o2.from.pn << 32) | o2.from.gp;\n        uint64_t t2 = ((uint64_t)o2.to.pn   << 32) | o2.to.gp;\n        if (f1 <= f2 && f2 <= t1) { // f2 is inside r1\n            if (t2 <= t1) { // r2 is fully inside r1\n                return r2;\n            } else { // r2 is partially inside r1\n                ui_edit_range_t r = {0};\n                r.from.pn = (int32_t)(f2 >> 32);\n                r.from.gp = (int32_t)(f2);\n                r.to.pn   = (int32_t)(t1 >> 32);\n                r.to.gp   = (int32_t)(t1);\n                return r;\n            }\n        } else if (f2 <= f1 && f1 <= t2) { // f1 is inside r2\n            if (t1 <= t2) { // r1 is fully inside r2\n                return r1;\n            } else { // r1 is partially inside r2\n                ui_edit_range_t r = {0};\n                r.from.pn = (int32_t)(f1 >> 32);\n                r.from.gp = (int32_t)(f1);\n                r.to.pn   = (int32_t)(t2 >> 32);\n                r.to.gp   = (int32_t)(t2);\n                return r;\n            }\n        } else {\n            return *ui_edit_range.invalid_range;\n        }\n    } else {\n        return *ui_edit_range.invalid_range;\n    }\n}\n\nstatic bool ui_edit_doc_realloc_ps_no_init(ui_edit_str_t* *ps,\n        int32_t old_np, int32_t new_np) { // reallocate paragraphs\n    for (int32_t i = new_np; i < old_np; i++) { ui_edit_str.free(&(*ps)[i]); }\n    bool ok = true;\n    if (new_np == 0) {\n        rt_heap.free(*ps);\n        *ps = null;\n    } else {\n        ok = rt_heap.realloc_zero((void**)ps, new_np * sizeof(ui_edit_str_t)) == 0;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_realloc_ps(ui_edit_str_t* *ps,\n        int32_t old_np, int32_t new_np) { // reallocate paragraphs\n    bool ok = ui_edit_doc_realloc_ps_no_init(ps, old_np, new_np);\n    if (ok) {\n        for (int32_t i = old_np; i < new_np; i++) {\n            ok = ui_edit_str.init(&(*ps)[i], null, 0, false);\n            rt_swear(ok, \"because .init(\\\"\\\", 0) does NOT allocate memory\");\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_init(ui_edit_text_t* t,\n        const char* s, int32_t b, bool heap) {\n    // When text comes from the source that lifetime is shorter\n    // than text itself (e.g. paste from clipboard) the parameter\n    // heap: true allows to make a copy of data on the heap\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    if (b < 0) { b = (int32_t)strlen(s); }\n    // if caller is concerned with best performance - it should pass b >= 0\n    int32_t np = 0; // number of paragraphs\n    int32_t n = rt_max(b / 64, 2); // initial number of allocated paragraphs\n    ui_edit_str_t* ps = null; // ps[n]\n    bool ok = ui_edit_doc_realloc_ps(&ps, 0, n);\n    if (ok) {\n        bool lf = false;\n        int32_t i = 0;\n        while (ok && i < b) {\n            int32_t k = i;\n            while (k < b && s[k] != '\\n') { k++; }\n            lf = k < b && s[k] == '\\n';\n            if (np >= n) {\n                int32_t n1_5 = n * 3 / 2; // n * 1.5\n                rt_assert(n1_5 > n);\n                ok = ui_edit_doc_realloc_ps(&ps, n, n1_5);\n                if (ok) { n = n1_5; }\n            }\n            if (ok) {\n                // insider knowledge about ui_edit_str allocation behaviour:\n                rt_assert(ps[np].c == 0 && ps[np].b == 0 &&\n                       ps[np].g2b[0] == 0);\n                ui_edit_str.free(&ps[np]);\n                // process \"\\r\\n\" strings\n                const int32_t e = k > i && s[k - 1] == '\\r' ? k - 1 : k;\n                const int32_t bytes = e - i; rt_assert(bytes >= 0);\n                const char* u = bytes == 0 ? null : s + i;\n                // str.init may allocate str.g2b[] on the heap and may fail\n                ok = ui_edit_str.init(&ps[np], u, bytes, heap && bytes > 0);\n                if (ok) { np++; }\n            }\n            i = k + lf;\n        }\n        if (ok && lf) { // last paragraph ended with line feed\n            if (np + 1 >= n) {\n                ok = ui_edit_doc_realloc_ps(&ps, n, n + 1);\n                if (ok) { n = n + 1; }\n            }\n            if (ok) { np++; }\n        }\n    }\n    if (ok && np == 0) { // special case empty string to a single paragraph\n        rt_assert(b <= 0 && (b == 0 || s[0] == 0x00));\n        np = 1; // ps[0] is already initialized as empty str\n        ok = ui_edit_doc_realloc_ps(&ps, n, 1);\n        rt_swear(ok, \"shrinking ps[] above\");\n    }\n    if (ok) {\n        rt_assert(np > 0);\n        t->np = np;\n        t->ps = ps;\n    } else if (ps != null) {\n        bool shrink = ui_edit_doc_realloc_ps(&ps, n, 0); // free()\n        rt_swear(shrink);\n        rt_heap.free(ps);\n        t->np = 0;\n        t->ps = null;\n    }\n    return ok;\n}\n\nstatic void ui_edit_text_dispose(ui_edit_text_t* t) {\n    if (t->np != 0) {\n        ui_edit_doc_realloc_ps(&t->ps, t->np, 0);\n        rt_assert(t->ps == null);\n        t->np = 0;\n    } else {\n        rt_assert(t->np == 0 && t->ps == null);\n    }\n}\n\nstatic void ui_edit_doc_dispose_to_do(ui_edit_to_do_t* to_do) {\n    if (to_do->text.np > 0) {\n        ui_edit_text_dispose(&to_do->text);\n    }\n    memset(&to_do->range, 0x00, sizeof(to_do->range));\n    ui_edit_check_zeros(to_do, sizeof(*to_do));\n}\n\nstatic int32_t ui_edit_text_bytes(const ui_edit_text_t* t,\n        const ui_edit_range_t* range) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    int32_t bytes = 0;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes += p->g2b[r.to.gp] - p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes += p->b - p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes += p->g2b[r.to.gp];\n        } else {\n            bytes += p->b;\n        }\n    }\n    return bytes;\n}\n\nstatic int32_t ui_edit_doc_bytes(const ui_edit_doc_t* d,\n        const ui_edit_range_t* r) {\n    return ui_edit_text.bytes(&d->text, r);\n}\n\nstatic int32_t ui_edit_doc_utf8bytes(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range) {\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    int32_t bytes = ui_edit_text.bytes(&d->text, &r);\n    // \"\\n\" after each paragraph and 0x00\n    return bytes + r.to.pn - r.from.pn + 1;\n}\n\nstatic void ui_edit_notify_before(ui_edit_doc_t* d,\n        const ui_edit_notify_info_t* ni) {\n    ui_edit_listener_t* o = d->listeners;\n    while (o != null) {\n        if (o->notify != null && o->notify->before != null) {\n            o->notify->before(o->notify, ni);\n        }\n        o = o->next;\n    }\n}\n\nstatic void ui_edit_notify_after(ui_edit_doc_t* d,\n        const ui_edit_notify_info_t* ni) {\n    ui_edit_listener_t* o = d->listeners;\n    while (o != null) {\n        if (o->notify != null && o->notify->after != null) {\n            o->notify->after(o->notify, ni);\n        }\n        o = o->next;\n    }\n}\n\nstatic bool ui_edit_doc_subscribe(ui_edit_doc_t* t, ui_edit_notify_t* notify) {\n    // TODO: not sure about double linked list.\n    // heap allocated resizable array may serve better and may be easier to maintain\n    bool ok = true;\n    ui_edit_listener_t* o = t->listeners;\n    if (o == null) {\n        ok = rt_heap.alloc_zero((void**)&t->listeners, sizeof(*o)) == 0;\n        if (ok) { o = t->listeners; }\n    } else {\n        while (o->next != null) { rt_swear(o->notify != notify); o = o->next; }\n        ok = rt_heap.alloc_zero((void**)&o->next, sizeof(*o)) == 0;\n        if (ok) { o->next->prev = o; o = o->next; }\n    }\n    if (ok) { o->notify = notify; }\n    return ok;\n}\n\nstatic void ui_edit_doc_unsubscribe(ui_edit_doc_t* t, ui_edit_notify_t* notify) {\n    ui_edit_listener_t* o = t->listeners;\n    bool removed = false;\n    while (o != null) {\n        ui_edit_listener_t* n = o->next;\n        if (o->notify == notify) {\n            rt_assert(!removed);\n            if (o->prev != null) { o->prev->next = n; }\n            if (o->next != null) { o->next->prev = o->prev; }\n            if (o == t->listeners) { t->listeners = n; }\n            rt_heap.free(o);\n            removed = true;\n        }\n        o = n;\n    }\n    rt_swear(removed);\n}\n\nstatic bool ui_edit_doc_copy_text(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range, ui_edit_text_t* t) {\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    ui_edit_check_range_inside_text(&d->text, &r);\n    int32_t np = r.to.pn - r.from.pn + 1;\n    bool ok = ui_edit_doc_realloc_ps(&t->ps, 0, np);\n    if (ok) { t->np = np; }\n    for (int32_t pn = r.from.pn; ok && pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &d->text.ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        rt_assert(t->ps[pn - r.from.pn].g == 0);\n        const char* u_or_null = bytes == 0 ? null : u;\n        ui_edit_str.replace(&t->ps[pn - r.from.pn], 0, 0, u_or_null, bytes);\n    }\n    if (!ok) {\n        ui_edit_text.dispose(t);\n        ui_edit_check_zeros(t, sizeof(*t));\n    }\n    return ok;\n}\n\nstatic void ui_edit_doc_copy(const ui_edit_doc_t* d,\n        const ui_edit_range_t* range, char* text, int32_t b) {\n    const ui_edit_range_t r = ui_edit_text.ordered(&d->text, range);\n    ui_edit_check_range_inside_text(&d->text, &r);\n    char* to = text;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &d->text.ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        const int32_t c = (int32_t)(uintptr_t)(to - text);\n        if (bytes > 0) {\n            rt_swear(c + bytes < b, \"c: %d bytes: %d b: %d\", c, bytes, b);\n            memmove(to, u, (size_t)bytes);\n            to += bytes;\n        }\n        if (pn < r.to.pn) {\n            rt_swear(c + bytes < b, \"c: %d bytes: %d b: %d\", c, bytes, b);\n            *to++ = '\\n';\n        }\n    }\n    const int32_t c = (int32_t)(uintptr_t)(to - text);\n    rt_swear(c + 1 == b, \"c: %d b: %d\", c, b);\n    *to++ = 0x00;\n}\n\nstatic bool ui_edit_text_insert_2_or_more(ui_edit_text_t* t, int32_t pn,\n        const ui_edit_str_t* s, const ui_edit_text_t* insert,\n        const ui_edit_str_t* e) {\n    // insert 2 or more paragraphs\n    rt_assert(0 <= pn && pn < t->np);\n    const int32_t np = t->np + insert->np - 1;\n    rt_assert(np > 0);\n    ui_edit_str_t* ps = null; // ps[np]\n    bool ok = ui_edit_doc_realloc_ps_no_init(&ps, 0, np);\n    if (ok) {\n        memmove(ps, t->ps, (size_t)pn * sizeof(ui_edit_str_t));\n        // `s` first line of `insert`\n        ok = ui_edit_str.init(&ps[pn], s->u, s->b, true);\n        // lines of `insert` between `s` and `e`\n        for (int32_t i = 1; ok && i < insert->np - 1; i++) {\n            ok = ui_edit_str.init(&ps[pn + i], insert->ps[i].u,\n                                               insert->ps[i].b, true);\n        }\n        // `e` last line of `insert`\n        if (ok) {\n            const int32_t ix = pn + insert->np - 1; // last `insert` index\n            ok = ui_edit_str.init(&ps[ix], e->u, e->b, true);\n        }\n        rt_assert(t->np - pn - 1 >= 0);\n        memmove(ps + pn + insert->np, t->ps + pn + 1,\n               (size_t)(t->np - pn - 1) * sizeof(ui_edit_str_t));\n        if (ok) {\n            // this two regions where moved to `ps`\n            memset(t->ps, 0x00, pn * sizeof(ui_edit_str_t));\n            memset(t->ps + pn + 1, 0x00,\n                   (size_t)(t->np - pn - 1) * sizeof(ui_edit_str_t));\n            // deallocate what was copied from `insert`\n            ui_edit_doc_realloc_ps_no_init(&t->ps, t->np, 0);\n            t->np = np;\n            t->ps = ps;\n        } else { // free allocated memory:\n            ui_edit_doc_realloc_ps_no_init(&ps, np, 0);\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert_1(ui_edit_text_t* t,\n        const ui_edit_pg_t ip, // insertion point\n        const ui_edit_text_t* insert) {\n    rt_assert(0 <= ip.pn && ip.pn < t->np);\n    ui_edit_str_t* str = &t->ps[ip.pn]; // string in document text\n    rt_assert(insert->np == 1);\n    ui_edit_str_t* ins = &insert->ps[0]; // string to insert\n    rt_assert(0 <= ip.gp && ip.gp <= str->g);\n    // ui_edit_str.replace() is all or nothing:\n    return ui_edit_str.replace(str, ip.gp, ip.gp, ins->u, ins->b);\n}\n\nstatic bool ui_edit_substr_append(ui_edit_str_t* d, const ui_edit_str_t* s1,\n    int32_t gp1, const ui_edit_str_t* s2) { // s1[0:gp1] + s2\n    rt_assert(d != s1 && d != s2);\n    const int32_t b = s1->g2b[gp1];\n    bool ok = ui_edit_str.init(d, b == 0 ? null : s1->u, b, true);\n    if (ok) {\n        ok = ui_edit_str.replace(d, d->g, d->g, s2->u, s2->b);\n    } else {\n        *d = *ui_edit_str.empty;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_append_substr(ui_edit_str_t* d, const ui_edit_str_t* s1,\n    const ui_edit_str_t* s2, int32_t gp2) {  // s1 + s2[gp1:*]\n    rt_assert(d != s1 && d != s2);\n    bool ok = ui_edit_str.init(d, s1->b == 0 ? null : s1->u, s1->b, true);\n    if (ok) {\n        const int32_t o = s2->g2b[gp2]; // offset (bytes)\n        const int32_t b = s2->b - o;\n        ok = ui_edit_str.replace(d, d->g, d->g, b == 0 ? null : s2->u + o, b);\n    } else {\n        *d = *ui_edit_str.empty;\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert(ui_edit_text_t* t, const ui_edit_pg_t ip,\n        const ui_edit_text_t* i) {\n    bool ok = true;\n    if (ok) {\n        if (i->np == 1) {\n            ok = ui_edit_text_insert_1(t, ip, i);\n        } else {\n            ui_edit_str_t* str = &t->ps[ip.pn];\n            ui_edit_str_t s = {0}; // start line of insert text `i`\n            ui_edit_str_t e = {0}; // end   line\n            if (ui_edit_substr_append(&s, str, ip.gp, &i->ps[0])) {\n                if (ui_edit_append_substr(&e, &i->ps[i->np - 1], str, ip.gp)) {\n                    ok = ui_edit_text_insert_2_or_more(t, ip.pn, &s, i, &e);\n                    ui_edit_str.free(&e);\n                }\n                ui_edit_str.free(&s);\n            }\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_remove_lines(ui_edit_text_t* t,\n    ui_edit_str_t* merge, int32_t from, int32_t to) {\n    bool ok = true;\n    for (int32_t pn = from + 1; pn <= to; pn++) {\n        ui_edit_str.free(&t->ps[pn]);\n    }\n    if (t->np - to - 1 > 0) {\n        memmove(&t->ps[from + 1], &t->ps[to + 1],\n                (size_t)(t->np - to - 1) * sizeof(ui_edit_str_t));\n    }\n    t->np -= to - from;\n    if (ok) {\n        ui_edit_str.swap(&t->ps[from], merge);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_insert_remove(ui_edit_text_t* t,\n        const ui_edit_range_t r, const ui_edit_text_t* i) {\n    bool ok = true;\n    ui_edit_str_t merge = {0};\n    const ui_edit_str_t* s = &t->ps[r.from.pn];\n    const ui_edit_str_t* e = &t->ps[r.to.pn];\n    const int32_t o = e->g2b[r.to.gp];\n    const int32_t b = e->b - o;\n    const char* u = b == 0 ? null : e->u + o;\n    ok = ui_edit_substr_append(&merge, s, r.from.gp, &i->ps[i->np - 1]) &&\n         ui_edit_str.replace(&merge, merge.g, merge.g, u, b);\n    if (ok) {\n        const bool empty_text = i->np == 1 && i->ps[0].g == 0;\n        if (!empty_text) {\n            ok = ui_edit_text_insert(t, r.to, i);\n        }\n        if (ok) {\n            ok = ui_edit_text_remove_lines(t, &merge, r.from.pn, r.to.pn);\n        }\n    }\n    if (merge.c > 0 || merge.g > 0) { ui_edit_str.free(&merge); }\n    return ok;\n}\n\nstatic bool ui_edit_text_copy_text(const ui_edit_text_t* t,\n        const ui_edit_range_t* range, ui_edit_text_t* to) {\n    ui_edit_check_zeros(to, sizeof(*to));\n    memset(to, 0x00, sizeof(*to));\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    int32_t np = r.to.pn - r.from.pn + 1;\n    bool ok = ui_edit_doc_realloc_ps(&to->ps, 0, np);\n    if (ok) { to->np = np; }\n    for (int32_t pn = r.from.pn; ok && pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        rt_assert(to->ps[pn - r.from.pn].g == 0);\n        const char* u_or_null = bytes == 0 ? null : u;\n        ui_edit_str.replace(&to->ps[pn - r.from.pn], 0, 0, u_or_null, bytes);\n    }\n    if (!ok) {\n        ui_edit_text.dispose(to);\n        ui_edit_check_zeros(to, sizeof(*to));\n    }\n    return ok;\n}\n\nstatic void ui_edit_text_copy(const ui_edit_text_t* t,\n        const ui_edit_range_t* range, char* text, int32_t b) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_check_range_inside_text(t, &r);\n    char* to = text;\n    for (int32_t pn = r.from.pn; pn <= r.to.pn; pn++) {\n        const ui_edit_str_t* p = &t->ps[pn];\n        const char* u = p->u;\n        int32_t bytes = 0;\n        if (pn == r.from.pn && pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp] - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.from.pn) {\n            bytes = p->b - p->g2b[r.from.gp];\n            u += p->g2b[r.from.gp];\n        } else if (pn == r.to.pn) {\n            bytes = p->g2b[r.to.gp];\n        } else {\n            bytes = p->b;\n        }\n        const int32_t c = (int32_t)(uintptr_t)(to - text);\n        rt_swear(c + bytes < b, \"d: %d bytes:%d b: %d\", c, bytes, b);\n        if (bytes > 0) {\n            memmove(to, u, (size_t)bytes);\n            to += bytes;\n        }\n        if (pn < r.to.pn) {\n            rt_swear(c + bytes + 1 < b, \"d: %d bytes:%d b: %d\", c, bytes, b);\n            *to++ = '\\n';\n        }\n    }\n    const int32_t c = (int32_t)(uintptr_t)(to - text);\n    rt_swear(c + 1 == b, \"d: %d b: %d\", c, b);\n    *to++ = 0x00;\n}\n\nstatic bool ui_edit_text_replace(ui_edit_text_t* t,\n        const ui_edit_range_t* range, const ui_edit_text_t* i,\n        ui_edit_to_do_t* undo) {\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    bool ok = undo == null ? true : ui_edit_text.copy_text(t, &r, &undo->text);\n    ui_edit_range_t x = r;\n    if (ok) {\n        if (ui_edit_range.is_empty(r)) {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = i->np == 1 ? r.from.gp + i->ps[0].g : i->ps[i->np - 1].g;\n            ok = ui_edit_text_insert(t, r.from, i);\n        } else if (i->np == 1 && r.from.pn == r.to.pn) {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = r.from.gp + i->ps[0].g;\n            ok = ui_edit_str.replace(&t->ps[r.from.pn],\n                    r.from.gp, r.to.gp, i->ps[0].u, i->ps[0].b);\n        } else {\n            x.to.pn = r.from.pn + i->np - 1;\n            x.to.gp = i->np == 1 ? r.from.gp + i->ps[0].g : i->ps[0].g;\n            ok = ui_edit_text_insert_remove(t, r, i);\n        }\n    }\n    if (undo != null) { undo->range = x; }\n    return ok;\n}\n\nstatic bool ui_edit_text_replace_utf8(ui_edit_text_t* t,\n        const ui_edit_range_t* range,\n        const char* utf8, int32_t b,\n        ui_edit_to_do_t* undo) {\n    if (b < 0) { b = (int32_t)strlen(utf8); }\n    ui_edit_text_t i = {0};\n    bool ok = ui_edit_text.init(&i, utf8, b, false);\n    if (ok) {\n        ok = ui_edit_text.replace(t, range, &i, undo);\n        ui_edit_text.dispose(&i);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_dup(ui_edit_text_t* t, const ui_edit_text_t* s) {\n    ui_edit_check_zeros(t, sizeof(*t));\n    memset(t, 0x00, sizeof(*t));\n    bool ok = ui_edit_doc_realloc_ps(&t->ps, 0, s->np);\n    if (ok) {\n        t->np = s->np;\n        for (int32_t i = 0; ok && i < s->np; i++) {\n            const ui_edit_str_t* p = &s->ps[i];\n            ok = ui_edit_str.replace(&t->ps[i], 0, 0, p->u, p->b);\n        }\n    }\n    if (!ok) {\n        ui_edit_text.dispose(t);\n    }\n    return ok;\n}\n\nstatic bool ui_edit_text_equal(const ui_edit_text_t* t1,\n        const ui_edit_text_t* t2) {\n    bool equal =  t1->np != t2->np;\n    for (int32_t i = 0; equal && i < t1->np; i++) {\n        const ui_edit_str_t* p1 = &t1->ps[i];\n        const ui_edit_str_t* p2 = &t2->ps[i];\n        equal = p1->b == p2->b &&\n                memcmp(p1->u, p2->u, p1->b) == 0;\n    }\n    return equal;\n}\n\nstatic void ui_edit_doc_before_replace_text(ui_edit_doc_t* d,\n        const ui_edit_range_t r, const ui_edit_text_t* t) {\n    ui_edit_check_range_inside_text(&d->text, &r);\n    ui_edit_range_t x = r;\n    x.to.pn = r.from.pn + t->np - 1;\n    if (r.from.pn == r.to.pn && t->np == 1) {\n        x.to.gp = r.from.gp + t->ps[0].g;\n    } else {\n        x.to.gp = t->ps[t->np - 1].g;\n    }\n    const ui_edit_notify_info_t ni_before = {\n        .ok = true, .d = d, .r = &r, .x = &x, .t = t,\n        .pnf = r.from.pn, .pnt = r.to.pn,\n        .deleted = 0, .inserted = 0\n    };\n    ui_edit_notify_before(d, &ni_before);\n}\n\nstatic void ui_edit_doc_after_replace_text(ui_edit_doc_t* d,\n        bool ok,\n        const ui_edit_range_t r,\n        const ui_edit_range_t x,\n        const ui_edit_text_t* t) {\n    const ui_edit_notify_info_t ni_after = {\n        .ok = ok, .d = d, .r = &r, .x = &x, .t = t,\n        .pnf = r.from.pn, .pnt = x.to.pn,\n        .deleted = r.to.pn - r.from.pn,\n        .inserted = t->np - 1\n    };\n    ui_edit_notify_after(d, &ni_after);\n}\n\nstatic bool ui_edit_doc_replace_text(ui_edit_doc_t* d,\n        const ui_edit_range_t* range, const ui_edit_text_t* i,\n        ui_edit_to_do_t* undo) {\n    ui_edit_text_t* t = &d->text;\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_doc_before_replace_text(d, r, i);\n    bool ok = ui_edit_text.replace(t, &r, i, undo);\n    ui_edit_doc_after_replace_text(d, ok, r, undo->range, i);\n    return ok;\n}\n\nstatic bool ui_edit_doc_replace_undoable(ui_edit_doc_t* d,\n        const ui_edit_range_t* r, const ui_edit_text_t* t,\n        ui_edit_to_do_t* undo) {\n    bool ok = ui_edit_doc_replace_text(d, r, t, undo);\n    if (ok && undo != null) {\n        undo->next = d->undo;\n        d->undo = undo;\n        // redo stack is not valid after new replace, empty it:\n        while (d->redo != null) {\n            ui_edit_to_do_t* next = d->redo->next;\n            d->redo->next = null;\n            ui_edit_doc.dispose_to_do(d->redo);\n            rt_heap.free(d->redo);\n            d->redo = next;\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_utf8_to_heap_text(const char* u, int32_t b,\n        ui_edit_text_t* it) {\n    rt_assert((b == 0) == (u == null || u[0] == 0x00));\n    return ui_edit_text.init(it, b != 0 ? u : null, b, true);\n}\n\n\nstatic bool ui_edit_doc_coalesce_undo(ui_edit_doc_t* d, ui_edit_text_t* i) {\n    ui_edit_to_do_t* undo = d->undo;\n    ui_edit_to_do_t* next = undo->next;\n//  rt_println(\"i: %.*s\", i->ps[0].b, i->ps[0].u);\n//  if (i->np == 1 && i->ps[0].g == 1) {\n//      rt_println(\"an: %d\", ui_edit_str.is_letter(rt_str.utf32(i->ps[0].u, i->ps[0].b)));\n//  }\n    bool coalesced = false;\n    const bool alpha_numeric = i->np == 1 && i->ps[0].g == 1 &&\n        ui_edit_str.is_letter(rt_str.utf32(i->ps[0].u, i->ps[0].b));\n    if (alpha_numeric && next != null) {\n        const ui_edit_range_t ur = undo->range;\n        const ui_edit_text_t* ut = &undo->text;\n        const ui_edit_range_t nr = next->range;\n        const ui_edit_text_t* nt = &next->text;\n//      rt_println(\"next: \\\"%.*s\\\" %d:%d..%d:%d undo: \\\"%.*s\\\" %d:%d..%d:%d\",\n//          nt->ps[0].b, nt->ps[0].u, nr.from.pn, nr.from.gp, nr.to.pn, nr.to.gp,\n//          ut->ps[0].b, ut->ps[0].u, ur.from.pn, ur.from.gp, ur.to.pn, ur.to.gp);\n        const bool c =\n            nr.from.pn == nr.to.pn && ur.from.pn == ur.to.pn &&\n            nr.from.pn == ur.from.pn &&\n            ut->np == 1 && ut->ps[0].g == 0 &&\n            nt->np == 1 && nt->ps[0].g == 0 &&\n            nr.to.gp == ur.from.gp && nr.to.gp > 0;\n        if (c) {\n            const ui_edit_str_t* str = &d->text.ps[nr.from.pn];\n            const int32_t* g2b = str->g2b;\n            const char* utf8 = str->u + g2b[nr.to.gp - 1];\n            uint32_t utf32 = rt_str.utf32(utf8, g2b[nr.to.gp] - g2b[nr.to.gp - 1]);\n            coalesced = ui_edit_str.is_letter(utf32);\n        }\n        if (coalesced) {\n//          rt_println(\"coalesced\");\n            next->range.to.gp++;\n            d->undo = next;\n            undo->next = null;\n            coalesced = true;\n        }\n    }\n    return coalesced;\n}\n\nstatic bool ui_edit_doc_replace(ui_edit_doc_t* d,\n        const ui_edit_range_t* range, const char* u, int32_t b) {\n    ui_edit_text_t* t = &d->text;\n    const ui_edit_range_t r = ui_edit_text.ordered(t, range);\n    ui_edit_to_do_t* undo = null;\n    bool ok = rt_heap.alloc_zero((void**)&undo, sizeof(ui_edit_to_do_t)) == 0;\n    if (ok) {\n        ui_edit_text_t i = {0};\n        ok = ui_edit_utf8_to_heap_text(u, b, &i);\n        if (ok) {\n            ok = ui_edit_doc_replace_undoable(d, &r, &i, undo);\n            if (ok) {\n                if (ui_edit_doc_coalesce_undo(d, &i)) {\n                    ui_edit_doc.dispose_to_do(undo);\n                    rt_heap.free(undo);\n                    undo = null;\n                }\n            }\n            ui_edit_text.dispose(&i);\n        }\n        if (!ok) {\n            ui_edit_doc.dispose_to_do(undo);\n            rt_heap.free(undo);\n            undo = null;\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_do(ui_edit_doc_t* d, ui_edit_to_do_t* to_do,\n        ui_edit_to_do_t* *stack) {\n    const ui_edit_range_t* r = &to_do->range;\n    ui_edit_to_do_t* redo = null;\n    bool ok = rt_heap.alloc_zero((void**)&redo, sizeof(ui_edit_to_do_t)) == 0;\n    if (ok) {\n        ok = ui_edit_doc_replace_text(d, r, &to_do->text, redo);\n        if (ok) {\n            ui_edit_doc.dispose_to_do(to_do);\n            rt_heap.free(to_do);\n        }\n        if (ok) {\n            redo->next = *stack;\n            *stack = redo;\n        } else {\n            if (redo != null) {\n                ui_edit_doc.dispose_to_do(redo);\n                rt_heap.free(redo);\n            }\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_doc_redo(ui_edit_doc_t* d) {\n    ui_edit_to_do_t* to_do = d->redo;\n    if (to_do == null) {\n        return false;\n    } else {\n        d->redo = d->redo->next;\n        to_do->next = null;\n        return ui_edit_doc_do(d, to_do, &d->undo);\n    }\n}\n\nstatic bool ui_edit_doc_undo(ui_edit_doc_t* d) {\n    ui_edit_to_do_t* to_do = d->undo;\n    if (to_do == null) {\n        return false;\n    } else {\n        d->undo = d->undo->next;\n        to_do->next = null;\n        return ui_edit_doc_do(d, to_do, &d->redo);\n    }\n}\n\nstatic bool ui_edit_doc_init(ui_edit_doc_t* d, const char* utf8,\n        int32_t bytes, bool heap) {\n    bool ok = true;\n    ui_edit_check_zeros(d, sizeof(*d));\n    memset(d, 0x00, sizeof(d));\n    if (bytes < 0) {\n        size_t n = strlen(utf8);\n        rt_swear(n < INT32_MAX);\n        bytes = (int32_t)n;\n    }\n    rt_assert((utf8 == null) == (bytes == 0));\n    if (ok) {\n        if (bytes == 0) { // empty string\n            ok = rt_heap.alloc_zero((void**)&d->text.ps, sizeof(ui_edit_str_t)) == 0;\n            if (ok) {\n                d->text.np = 1;\n                ok = ui_edit_str.init(&d->text.ps[0], null, 0, false);\n            }\n        } else {\n            ok = ui_edit_text.init(&d->text, utf8, bytes, heap);\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_doc_dispose(ui_edit_doc_t* d) {\n    for (int32_t i = 0; i < d->text.np; i++) {\n        ui_edit_str.free(&d->text.ps[i]);\n    }\n    if (d->text.ps != null) {\n        rt_heap.free(d->text.ps);\n        d->text.ps = null;\n    }\n    d->text.np  = 0;\n    while (d->undo != null) {\n        ui_edit_to_do_t* next = d->undo->next;\n        d->undo->next = null;\n        ui_edit_doc.dispose_to_do(d->undo);\n        rt_heap.free(d->undo);\n        d->undo = next;\n    }\n    while (d->redo != null) {\n        ui_edit_to_do_t* next = d->redo->next;\n        d->redo->next = null;\n        ui_edit_doc.dispose_to_do(d->redo);\n        rt_heap.free(d->redo);\n        d->redo = next;\n    }\n    rt_assert(d->listeners == null, \"unsubscribe listeners?\");\n    while (d->listeners != null) {\n        ui_edit_listener_t* next = d->listeners->next;\n        d->listeners->next = null;\n        rt_heap.free(d->listeners->next);\n        d->listeners = next;\n    }\n    ui_edit_check_zeros(d, sizeof(*d));\n}\n\n// ui_edit_str\n\nstatic int32_t ui_edit_str_g2b_ascii[1024]; // ui_edit_str_g2b_ascii[i] == i for all \"i\"\nstatic char    ui_edit_str_empty_utf8[1] = {0x00};\n\nstatic const ui_edit_str_t ui_edit_str_empty = {\n    .u = ui_edit_str_empty_utf8,\n    .g2b = ui_edit_str_g2b_ascii,\n    .c = 0, .b = 0, .g = 0\n};\n\nstatic bool    ui_edit_str_init(ui_edit_str_t* s, const char* u, int32_t b, bool heap);\nstatic void    ui_edit_str_swap(ui_edit_str_t* s1, ui_edit_str_t* s2);\nstatic int32_t ui_edit_str_gp_to_bp(const char* s, int32_t bytes, int32_t gp);\nstatic int32_t ui_edit_str_bytes(ui_edit_str_t* s, int32_t f, int32_t t);\nstatic bool    ui_edit_str_expand(ui_edit_str_t* s, int32_t c);\nstatic void    ui_edit_str_shrink(ui_edit_str_t* s);\nstatic bool    ui_edit_str_replace(ui_edit_str_t* s, int32_t f, int32_t t,\n                                   const char* u, int32_t b);\n\n//  bool (*is_zwj)(uint32_t utf32); // zero width joiner\n//  bool (*is_letter)(uint32_t utf32); // in European Alphabets\n//  bool (*is_digit)(uint32_t utf32);\n//  bool (*is_symbol)(uint32_t utf32);\n//  bool (*is_alphanumeric)(uint32_t utf32);\n//  bool (*is_blank)(uint32_t utf32); // white space\n//  bool (*is_punctuation)(uint32_t utf32);\n//  bool (*is_combining)(uint32_t utf32);\n//  bool (*is_spacing)(uint32_t utf32); // spacing modifiers\n//  bool (*is_cjk_or_emoji)(uint32_t utf32);\n\nstatic bool ui_edit_str_is_zwj(uint32_t utf32);\nstatic bool ui_edit_str_is_letter(uint32_t utf32);\nstatic bool ui_edit_str_is_digit(uint32_t utf32);\nstatic bool ui_edit_str_is_symbol(uint32_t utf32);\nstatic bool ui_edit_str_is_alphanumeric(uint32_t utf32);\nstatic bool ui_edit_str_is_blank(uint32_t utf32);\nstatic bool ui_edit_str_is_punctuation(uint32_t utf32);\nstatic bool ui_edit_str_is_combining(uint32_t utf32);\nstatic bool ui_edit_str_is_spacing(uint32_t utf32);\nstatic bool ui_edit_str_is_blank(uint32_t utf32);\nstatic bool ui_edit_str_is_cjk_or_emoji(uint32_t utf32);\nstatic bool ui_edit_str_can_break(uint32_t cp1, uint32_t cp2);\n\nstatic void    ui_edit_str_test(void);\nstatic void    ui_edit_str_free(ui_edit_str_t* s);\n\nui_edit_str_if ui_edit_str = {\n    .init            = ui_edit_str_init,\n    .swap            = ui_edit_str_swap,\n    .gp_to_bp        = ui_edit_str_gp_to_bp,\n    .bytes           = ui_edit_str_bytes,\n    .expand          = ui_edit_str_expand,\n    .shrink          = ui_edit_str_shrink,\n    .replace         = ui_edit_str_replace,\n    .is_zwj          = ui_edit_str_is_zwj,\n    .is_letter       = ui_edit_str_is_letter,\n    .is_digit        = ui_edit_str_is_digit,\n    .is_symbol       = ui_edit_str_is_symbol,\n    .is_alphanumeric = ui_edit_str_is_alphanumeric,\n    .is_blank        = ui_edit_str_is_blank,\n    .is_punctuation  = ui_edit_str_is_punctuation,\n    .is_combining    = ui_edit_str_is_combining,\n    .is_spacing      = ui_edit_str_is_spacing,\n    .is_punctuation  = ui_edit_str_is_punctuation,\n    .is_cjk_or_emoji = ui_edit_str_is_cjk_or_emoji,\n    .can_break       = ui_edit_str_can_break,\n    .test            = ui_edit_str_test,\n    .free            = ui_edit_str_free,\n    .empty           = &ui_edit_str_empty\n};\n\n#pragma push_macro(\"ui_edit_str_check\")\n#pragma push_macro(\"ui_edit_str_check_from_to\")\n#pragma push_macro(\"ui_edit_check_zeros\")\n#pragma push_macro(\"ui_edit_str_check_empty\")\n#pragma push_macro(\"ui_edit_str_parameters\")\n\n#ifdef DEBUG\n\n#define ui_edit_str_check(s) do {                                   \\\n    /* check the s struct constrains */                             \\\n    rt_assert(s->b >= 0);                                              \\\n    rt_assert(s->c == 0 || s->c >= s->b);                              \\\n    rt_assert(s->g >= 0);                                              \\\n    /* s->g2b[] may be null (not heap allocated) when .b == 0 */    \\\n    if (s->g == 0) { rt_assert(s->b == 0); }                           \\\n    if (s->g > 0) {                                                 \\\n        rt_assert(s->g2b[0] == 0 && s->g2b[s->g] == s->b);             \\\n    }                                                               \\\n    for (int32_t i = 1; i < s->g; i++) {                            \\\n        rt_assert(0 < s->g2b[i] - s->g2b[i - 1] &&                     \\\n                   s->g2b[i] - s->g2b[i - 1] <= 4);                 \\\n        rt_assert(s->g2b[i] - s->g2b[i - 1] ==                         \\\n            rt_str.utf8bytes(                                 \\\n            s->u + s->g2b[i - 1], s->g2b[i] - s->g2b[i - 1]));      \\\n    }                                                               \\\n} while (0)\n\n#define ui_edit_str_check_from_to(s, f, t) do {                     \\\n    rt_assert(0 <= f && f <= s->g);                                    \\\n    rt_assert(0 <= t && t <= s->g);                                    \\\n    rt_assert(f <= t);                                                 \\\n} while (0)\n\n#define ui_edit_str_check_empty(u, b) do {                          \\\n    if (b == 0) { rt_assert(u != null && u[0] == 0x00); }              \\\n    if (u == null || u[0] == 0x00) { rt_assert(b == 0); }              \\\n} while (0)\n\n\n\n#else\n\n#define ui_edit_str_check(s)               do { } while (0)\n#define ui_edit_str_check_from_to(s, f, t) do { } while (0)\n#define ui_edit_str_check_empty(u, b)      do { } while (0)\n\n#endif\n\n// ui_edit_str_foo(*, \"...\", -1) treat as 0x00 terminated\n// ui_edit_str_foo(*, null, 0) treat as (\"\", 0)\n\n#define ui_edit_str_parameters(u, b) do {                           \\\n    if (u == null) { u = ui_edit_str_empty_utf8; }                  \\\n    if (b < 0)  {                                                   \\\n        rt_assert(strlen(u) < INT32_MAX);                              \\\n        b = (int32_t)strlen(u);                                     \\\n    }                                                               \\\n    ui_edit_str_check_empty(u, b);                                  \\\n} while (0)\n\nstatic int32_t ui_edit_str_gp_to_bp(const char* utf8, int32_t bytes, int32_t gp) {\n    rt_swear(bytes >= 0);\n    bool ok = true;\n    int32_t c = 0;\n    int32_t i = 0;\n    if (bytes > 0) {\n        while (c < gp && ok) {\n            rt_assert(i < bytes);\n            const int32_t b = rt_str.utf8bytes(utf8 + i, bytes - i);\n            ok = 0 < b && i + b <= bytes;\n            if (ok) { i += b; c++; }\n        }\n    }\n    rt_assert(i <= bytes);\n    return ok ? i : -1;\n}\n\nstatic void ui_edit_str_free(ui_edit_str_t* s) {\n    if (s->g2b != null && s->g2b != ui_edit_str_g2b_ascii) {\n        rt_heap.free(s->g2b);\n    } else {\n        #ifdef UI_EDIT_STR_TEST // check ui_edit_str_g2b_ascii integrity\n            for (int32_t i = 0; i < rt_countof(ui_edit_str_g2b_ascii); i++) {\n                rt_assert(ui_edit_str_g2b_ascii[i] == i);\n            }\n        #endif\n    }\n    s->g2b = null;\n    s->g = 0;\n    if (s->c > 0) {\n        rt_heap.free(s->u);\n        s->u = null;\n        s->c = 0;\n        s->b = 0;\n    } else {\n        s->u = null;\n        s->b = 0;\n    }\n    ui_edit_check_zeros(s, sizeof(*s));\n}\n\nstatic bool ui_edit_str_init_g2b(ui_edit_str_t* s) {\n    const int64_t _4_bytes = (int64_t)sizeof(int32_t);\n    // start with number of glyphs == number of bytes (ASCII text):\n    bool ok = rt_heap.alloc(&s->g2b, (size_t)(s->b + 1) * _4_bytes) == 0;\n    int32_t i = 0; // index in u[] string\n    int32_t k = 1; // glyph number\n    // g2b[k] start postion in uint8_t offset from utf8 text of glyph[k]\n    while (i < s->b && ok) {\n        const int32_t b = rt_str.utf8bytes(s->u + i, s->b - i);\n        ok = b > 0 && i + b <= s->b;\n        if (ok) {\n            i += b;\n            s->g2b[k] = i;\n            k++;\n        }\n    }\n    if (ok) {\n        rt_assert(0 < k && k <= s->b + 1);\n        s->g2b[0] = 0;\n        rt_assert(s->g2b[k - 1] == s->b);\n        s->g = k - 1;\n        if (k < s->b + 1) {\n            ok = rt_heap.realloc(&s->g2b, k * _4_bytes) == 0;\n            rt_assert(ok, \"shrinking - should always be ok\");\n        }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_str_init(ui_edit_str_t* s, const char* u, int32_t b,\n        bool heap) {\n    enum { n = rt_countof(ui_edit_str_g2b_ascii) };\n    if (ui_edit_str_g2b_ascii[n - 1] != n - 1) {\n        for (int32_t i = 0; i < n; i++) { ui_edit_str_g2b_ascii[i] = i; }\n    }\n    bool ok = true;\n    ui_edit_check_zeros(s, sizeof(*s)); // caller must zero out\n    memset(s, 0x00, sizeof(*s));\n    ui_edit_str_parameters(u, b);\n    if (b == 0) { // cast below intentionally removes \"const\" qualifier\n        s->g2b = (int32_t*)ui_edit_str_g2b_ascii;\n        s->u = (char*)u;\n        rt_assert(s->c == 0 && u[0] == 0x00);\n    } else {\n        if (heap) {\n            ok = rt_heap.alloc((void**)&s->u, b) == 0;\n            if (ok) { s->c = b; memmove(s->u, u, (size_t)b); }\n        } else {\n            s->u = (char*)u;\n        }\n        if (ok) {\n            s->b = b;\n            if (b == 1 && u[0] <= 0x7F) {\n                s->g2b = (int32_t*)ui_edit_str_g2b_ascii;\n                s->g = 1;\n            } else {\n                ok = ui_edit_str_init_g2b(s);\n            }\n        }\n    }\n    if (ok) { ui_edit_str.shrink(s); } else { ui_edit_str.free(s); }\n    return ok;\n}\n\nstatic void ui_edit_str_swap(ui_edit_str_t* s1, ui_edit_str_t* s2) {\n    ui_edit_str_t s = *s1; *s1 = *s2; *s2 = s;\n}\n\nstatic int32_t ui_edit_str_bytes(ui_edit_str_t* s,\n        int32_t f, int32_t t) { // glyph positions\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    return s->g2b[t] - s->g2b[f];\n}\n\nstatic bool ui_edit_str_move_g2b_to_heap(ui_edit_str_t* s) {\n    bool ok = true;\n    if (s->g2b == ui_edit_str_g2b_ascii) { // even for s->g == 0\n        if (s->b == s->g && s->g < rt_countof(ui_edit_str_g2b_ascii) - 1) {\n//          rt_println(\"forcefully moving to heap\");\n            // this is usually done in the process of concatenation\n            // of 2 ascii strings when result is known to be longer\n            // than rt_countof(ui_edit_str_g2b_ascii) - 1 but the\n            // first string in concatenation is short. It's OK.\n        }\n        const int32_t bytes = (s->g + 1) * (int32_t)sizeof(int32_t);\n        ok = rt_heap.alloc(&s->g2b, bytes) == 0;\n        if (ok) { memmove(s->g2b, ui_edit_str_g2b_ascii, (size_t)bytes); }\n    }\n    return ok;\n}\n\nstatic bool ui_edit_str_move_to_heap(ui_edit_str_t* s, int32_t c) {\n    bool ok = true;\n    rt_assert(c >= s->b, \"can expand cannot shrink\");\n    if (s->c == 0) { // s->u points outside of the heap\n        const char* o = s->u;\n        ok = rt_heap.alloc((void**)&s->u, c) == 0;\n        if (ok) { memmove(s->u, o, (size_t)s->b); }\n    } else if (s->c < c) {\n        ok = rt_heap.realloc((void**)&s->u, c) == 0;\n    }\n    if (ok) { s->c = c; }\n    return ok;\n}\n\nstatic bool ui_edit_str_expand(ui_edit_str_t* s, int32_t c) {\n    rt_swear(c > 0);\n    bool ok = ui_edit_str_move_to_heap(s, c);\n    if (ok && c > s->c) {\n        if (rt_heap.realloc((void**)&s->u, c) == 0) {\n            s->c = c;\n        } else {\n            ok = false;\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_str_shrink(ui_edit_str_t* s) {\n    if (s->c > s->b) { // s->c == 0 for empty and single byte ASCII strings\n        rt_assert(s->u != ui_edit_str_empty_utf8);\n        if (s->b == 0) {\n            rt_heap.free(s->u);\n            s->u = ui_edit_str_empty_utf8;\n        } else {\n            bool ok = rt_heap.realloc((void**)&s->u, s->b) == 0;\n            rt_swear(ok, \"smaller size is always expected to be ok\");\n        }\n        s->c = s->b;\n    }\n    // Optimize memory for short ASCII only strings:\n    if (s->g2b != ui_edit_str_g2b_ascii) {\n        if (s->g == s->b && s->g < rt_countof(ui_edit_str_g2b_ascii) - 1) {\n            // If this is an ascii only utf8 string shorter than\n            // ui_edit_str_g2b_ascii it does not need .g2b[] allocated:\n            if (s->g2b != ui_edit_str_g2b_ascii) {\n                rt_heap.free(s->g2b);\n                s->g2b = ui_edit_str_g2b_ascii;\n            }\n        } else {\n//          const int32_t b64 = rt_min(s->b, 64);\n//          rt_println(\"none ASCII: .b:%d .g:%d %*.*s\", s->b, s->g, b64, b64, s->u);\n        }\n    }\n}\n\nstatic bool ui_edit_str_remove(ui_edit_str_t* s, int32_t f, int32_t t) {\n    bool ok = true; // optimistic approach\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    const int32_t bytes_to_remove = s->g2b[t] - s->g2b[f];\n    rt_assert(bytes_to_remove >= 0);\n    if (bytes_to_remove > 0) {\n        ok = ui_edit_str_move_to_heap(s, s->b);\n        if (ok) {\n            const int32_t bytes_to_shift = s->b - s->g2b[t];\n            rt_assert(0 <= bytes_to_shift && bytes_to_shift <= s->b);\n            memmove(s->u + s->g2b[f], s->u + s->g2b[t], (size_t)bytes_to_shift);\n            if (s->g2b != ui_edit_str_g2b_ascii) {\n                memmove(s->g2b + f, s->g2b + t,\n                        (size_t)(s->g - t + 1) * sizeof(int32_t));\n                for (int32_t i = f; i <= s->g; i++) {\n                    s->g2b[i] -= bytes_to_remove;\n                }\n            } else {\n                // no need to shrink g2b[] for ASCII only strings:\n                for (int32_t i = 0; i <= s->g; i++) { rt_assert(s->g2b[i] == i); }\n            }\n            s->b -= bytes_to_remove;\n            s->g -= t - f;\n        }\n    }\n    ui_edit_str_check(s);\n    return ok;\n}\n\nstatic bool ui_edit_str_replace(ui_edit_str_t* s,\n        int32_t f, int32_t t, const char* u, int32_t b) {\n    const int64_t _4_bytes = (int64_t)sizeof(int32_t);\n    bool ok = true; // optimistic approach\n    ui_edit_str_check_from_to(s, f, t);\n    ui_edit_str_check(s);\n    ui_edit_str_parameters(u, b);\n    // we are inserting \"b\" bytes and removing \"t - f\" glyphs\n    const int32_t bytes_to_remove = s->g2b[t] - s->g2b[f];\n    const int32_t bytes_to_insert = b; // only for readability\n    if (b == 0) { // just remove glyphs\n        ok = ui_edit_str_remove(s, f, t);\n    } else { // remove and insert\n        ui_edit_str_t ins = {0};\n        // ui_edit_str_init_ro() verifies utf-8 and calculates g2b[]:\n        ok = ui_edit_str_init(&ins, u, b, false);\n        const int32_t glyphs_to_insert = ins.g; // only for readability\n        const int32_t glyphs_to_remove = t - f; // only for readability\n        if (ok) {\n            const int32_t bytes = s->b + bytes_to_insert - bytes_to_remove;\n            rt_assert(ins.g2b != null); // pacify code analysis\n            rt_assert(bytes > 0);\n            const int32_t c = rt_max(s->b, bytes);\n            // keep g2b == ui_edit_str_g2b_ascii as much as possible\n            const bool all_ascii = s->g2b == ui_edit_str_g2b_ascii &&\n                                   ins.g2b == ui_edit_str_g2b_ascii &&\n                                   bytes < rt_countof(ui_edit_str_g2b_ascii) - 1;\n            ok = ui_edit_str_move_to_heap(s, c);\n            if (ok) {\n                if (!all_ascii) {\n                    ui_edit_str_move_g2b_to_heap(s);\n                }\n                // insert ui_edit_str_t \"ins\" at glyph position \"f\"\n                // reusing ins.u[0..ins.b-1] and ins.g2b[0..ins.g]\n                // moving memory using memmove() left to right:\n                if (bytes_to_insert <= bytes_to_remove) {\n                    memmove(s->u + s->g2b[f] + bytes_to_insert,\n                           s->u + s->g2b[f] + bytes_to_remove,\n                           (size_t)(s->b - s->g2b[f] - bytes_to_remove));\n                    if (all_ascii) {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                    } else {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        memmove(s->g2b + f + glyphs_to_insert,\n                               s->g2b + f + glyphs_to_remove,\n                               (size_t)(s->g - t + 1) * _4_bytes);\n                    }\n                    memmove(s->u + s->g2b[f], ins.u, (size_t)ins.b);\n                } else {\n                    if (all_ascii) {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                    } else {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        const int32_t g = s->g + glyphs_to_insert -\n                                                 glyphs_to_remove;\n                        rt_assert(g > s->g);\n                        ok = rt_heap.realloc(&s->g2b,\n                                             (size_t)(g + 1) * _4_bytes) == 0;\n                    }\n                    // need to shift bytes staring with s.g2b[t] toward the end\n                    if (ok) {\n                        memmove(s->u + s->g2b[f] + bytes_to_insert,\n                                s->u + s->g2b[f] + bytes_to_remove,\n                                (size_t)(s->b - s->g2b[f] - bytes_to_remove));\n                        if (all_ascii) {\n                            rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        } else {\n                            rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                            memmove(s->g2b + f + glyphs_to_insert,\n                                    s->g2b + f + glyphs_to_remove,\n                                    (size_t)(s->g - t + 1) * _4_bytes);\n                        }\n                        memmove(s->u + s->g2b[f], ins.u, (size_t)ins.b);\n                    }\n                }\n                if (ok) {\n                    if (!all_ascii) {\n                        rt_assert(s->g2b != null && s->g2b != ui_edit_str_g2b_ascii);\n                        for (int32_t i = f; i <= f + glyphs_to_insert; i++) {\n                            s->g2b[i] = ins.g2b[i - f] + s->g2b[f];\n                        }\n                    } else {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        for (int32_t i = f; i <= f + glyphs_to_insert; i++) {\n                            rt_assert(ui_edit_str_g2b_ascii[i] == i);\n                            rt_assert(ins.g2b[i - f] + s->g2b[f] == i);\n                        }\n                    }\n                    s->b += bytes_to_insert - bytes_to_remove;\n                    s->g += glyphs_to_insert - glyphs_to_remove;\n                    rt_assert(s->b == bytes);\n                    if (!all_ascii) {\n                        rt_assert(s->g2b != ui_edit_str_g2b_ascii);\n                        for (int32_t i = f + glyphs_to_insert + 1; i <= s->g; i++) {\n                            s->g2b[i] += bytes_to_insert - bytes_to_remove;\n                        }\n                        s->g2b[s->g] = s->b;\n                    } else {\n                        rt_assert(s->g2b == ui_edit_str_g2b_ascii);\n                        for (int32_t i = f + glyphs_to_insert + 1; i <= s->g; i++) {\n                            rt_assert(s->g2b[i] == i);\n                            rt_assert(ui_edit_str_g2b_ascii[i] == i);\n                        }\n                        rt_assert(s->g2b[s->g] == s->b);\n                    }\n                }\n            }\n            ui_edit_str_free(&ins);\n        }\n    }\n    ui_edit_str_shrink(s);\n    ui_edit_str_check(s);\n    return ok;\n}\n\nstatic bool ui_edit_str_is_zwj(uint32_t utf32) {\n    return utf32 == 0x200D;\n}\n\nstatic bool ui_edit_str_is_punctuation(uint32_t utf32) {\n    return\n        (utf32 >= 0x0021 && utf32 <= 0x0023) ||  // !\"#\n        (utf32 >= 0x0025 && utf32 <= 0x002A) ||  // %&'()*+\n        (utf32 >= 0x002C && utf32 <= 0x002F) ||  // ,-./\n        (utf32 >= 0x003A && utf32 <= 0x003B) ||  //:;\n        (utf32 >= 0x003F && utf32 <= 0x0040) ||  // ?@\n        (utf32 >= 0x005B && utf32 <= 0x005D) ||  // [\\]\n        (utf32 == 0x005F) ||                     // _\n        (utf32 == 0x007B) ||                     // {\n        (utf32 == 0x007D) ||                     // }\n        (utf32 == 0x007E) ||                     // ~\n        (utf32 >= 0x2000 && utf32 <= 0x206F) ||  // General Punctuation\n        (utf32 >= 0x3000 && utf32 <= 0x303F) ||  // CJK Symbols and Punctuation\n        (utf32 >= 0xFE30 && utf32 <= 0xFE4F) ||  // CJK Compatibility Forms\n        (utf32 >= 0xFE50 && utf32 <= 0xFE6F) ||  // Small Form Variants\n        (utf32 >= 0xFF01 && utf32 <= 0xFF0F) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF1A && utf32 <= 0xFF1F) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF3B && utf32 <= 0xFF3D) ||  // Fullwidth ASCII variants\n        (utf32 == 0xFF3F) ||                     // Fullwidth _\n        (utf32 >= 0xFF5B && utf32 <= 0xFF65);    // Fullwidth ASCII variants and halfwidth forms\n}\n\nstatic bool ui_edit_str_is_letter(uint32_t utf32) {\n    return\n        (utf32 >= 0x0041 && utf32 <= 0x005A) ||  // Latin uppercase\n        (utf32 >= 0x0061 && utf32 <= 0x007A) ||  // Latin lowercase\n        (utf32 >= 0x00C0 && utf32 <= 0x00D6) ||  // Latin-1 uppercase\n        (utf32 >= 0x00D8 && utf32 <= 0x00F6) ||  // Latin-1 lowercase\n        (utf32 >= 0x00F8 && utf32 <= 0x00FF) ||  // Latin-1 lowercase\n        (utf32 >= 0x0100 && utf32 <= 0x017F) ||  // Latin Extended-A\n        (utf32 >= 0x0180 && utf32 <= 0x024F) ||  // Latin Extended-B\n        (utf32 >= 0x0250 && utf32 <= 0x02AF) ||  // IPA Extensions\n        (utf32 >= 0x0370 && utf32 <= 0x03FF) ||  // Greek and Coptic\n        (utf32 >= 0x0400 && utf32 <= 0x04FF) ||  // Cyrillic\n        (utf32 >= 0x0500 && utf32 <= 0x052F) ||  // Cyrillic Supplement\n        (utf32 >= 0x0530 && utf32 <= 0x058F) ||  // Armenian\n        (utf32 >= 0x10A0 && utf32 <= 0x10FF) ||  // Georgian\n        (utf32 >= 0x0600 && utf32 <= 0x06FF) ||  // Arabic (covers Arabic, Kurdish, and Pashto)\n        (utf32 >= 0x0900 && utf32 <= 0x097F) ||  // Devanagari (covers Hindi)\n        (utf32 >= 0x0980 && utf32 <= 0x09FF) ||  // Bengali\n        (utf32 >= 0x0A00 && utf32 <= 0x0A7F) ||  // Gurmukhi (common in Northern India, related to Punjabi)\n        (utf32 >= 0x0B80 && utf32 <= 0x0BFF) ||  // Tamil\n        (utf32 >= 0x0C00 && utf32 <= 0x0C7F) ||  // Telugu\n        (utf32 >= 0x0C80 && utf32 <= 0x0CFF) ||  // Kannada\n        (utf32 >= 0x0D00 && utf32 <= 0x0D7F) ||  // Malayalam\n        (utf32 >= 0x0D80 && utf32 <= 0x0DFF) ||  // Sinhala\n        (utf32 >= 0x3040 && utf32 <= 0x309F) ||  // Hiragana (because it is syllabic)\n        (utf32 >= 0x30A0 && utf32 <= 0x30FF) ||  // Katakana\n        (utf32 >= 0x1E00 && utf32 <= 0x1EFF);    // Latin Extended Additional\n}\n\nstatic bool ui_edit_str_is_spacing(uint32_t utf32) {\n    return\n        (utf32 >= 0x02B0 && utf32 <= 0x02FF) ||  // Spacing Modifier Letters\n        (utf32 >= 0xA700 && utf32 <= 0xA71F);    // Modifier Tone Letters\n}\n\nstatic bool ui_edit_str_is_combining(uint32_t utf32) {\n    return\n        (utf32 >= 0x0300 && utf32 <= 0x036F) ||  // Combining Diacritical Marks\n        (utf32 >= 0x1AB0 && utf32 <= 0x1AFF) ||  // Combining Diacritical Marks Extended\n        (utf32 >= 0x1DC0 && utf32 <= 0x1DFF) ||  // Combining Diacritical Marks Supplement\n        (utf32 >= 0x20D0 && utf32 <= 0x20FF) ||  // Combining Diacritical Marks for Symbols\n        (utf32 >= 0xFE20 && utf32 <= 0xFE2F);    // Combining Half Marks\n}\n\nstatic bool ui_edit_str_is_blank(uint32_t utf32) {\n    return\n        (utf32 == 0x0009) ||  // Horizontal Tab\n        (utf32 == 0x000A) ||  // Line Feed\n        (utf32 == 0x000B) ||  // Vertical Tab\n        (utf32 == 0x000C) ||  // Form Feed\n        (utf32 == 0x000D) ||  // Carriage Return\n        (utf32 == 0x0020) ||  // Space\n        (utf32 == 0x0085) ||  // Next Line\n        (utf32 == 0x00A0) ||  // Non-breaking Space\n        (utf32 == 0x1680) ||  // Ogham Space Mark\n        (utf32 >= 0x2000 && utf32 <= 0x200A) ||  // En Quad to Hair Space\n        (utf32 == 0x2028) ||  // Line Separator\n        (utf32 == 0x2029) ||  // Paragraph Separator\n        (utf32 == 0x202F) ||  // Narrow No-Break Space\n        (utf32 == 0x205F) ||  // Medium Mathematical Space\n        (utf32 == 0x3000);    // Ideographic Space\n}\n\nstatic bool ui_edit_str_is_symbol(uint32_t utf32) {\n    return\n        (utf32 >= 0x0024 && utf32 <= 0x0024) ||  // Dollar sign\n        (utf32 >= 0x00A2 && utf32 <= 0x00A5) ||  // Cent sign to Yen sign\n        (utf32 >= 0x20A0 && utf32 <= 0x20CF) ||  // Currency Symbols\n        (utf32 >= 0x2100 && utf32 <= 0x214F) ||  // Letter like Symbols\n        (utf32 >= 0x2190 && utf32 <= 0x21FF) ||  // Arrows\n        (utf32 >= 0x2200 && utf32 <= 0x22FF) ||  // Mathematical Operators\n        (utf32 >= 0x2300 && utf32 <= 0x23FF) ||  // Miscellaneous Technical\n        (utf32 >= 0x2400 && utf32 <= 0x243F) ||  // Control Pictures\n        (utf32 >= 0x2440 && utf32 <= 0x245F) ||  // Optical Character Recognition\n        (utf32 >= 0x2460 && utf32 <= 0x24FF) ||  // Enclosed Alphanumeric\n        (utf32 >= 0x2500 && utf32 <= 0x257F) ||  // Box Drawing\n        (utf32 >= 0x2580 && utf32 <= 0x259F) ||  // Block Elements\n        (utf32 >= 0x25A0 && utf32 <= 0x25FF) ||  // Geometric Shapes\n        (utf32 >= 0x2600 && utf32 <= 0x26FF) ||  // Miscellaneous Symbols\n        (utf32 >= 0x2700 && utf32 <= 0x27BF) ||  // Dingbats\n        (utf32 >= 0x2900 && utf32 <= 0x297F) ||  // Supplemental Arrows-B\n        (utf32 >= 0x2B00 && utf32 <= 0x2BFF) ||  // Miscellaneous Symbols and Arrows\n        (utf32 >= 0xFB00 && utf32 <= 0xFB4F) ||  // Alphabetic Presentation Forms\n        (utf32 >= 0xFE50 && utf32 <= 0xFE6F) ||  // Small Form Variants\n        (utf32 >= 0xFF01 && utf32 <= 0xFF20) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF3B && utf32 <= 0xFF40) ||  // Fullwidth ASCII variants\n        (utf32 >= 0xFF5B && utf32 <= 0xFF65);    // Fullwidth ASCII variants\n}\n\nstatic bool ui_edit_str_is_digit(uint32_t utf32) {\n    return\n        (utf32 >= 0x0030 && utf32 <= 0x0039) ||  // ASCII digits 0-9\n        (utf32 >= 0x0660 && utf32 <= 0x0669) ||  // Arabic-Indic digits\n        (utf32 >= 0x06F0 && utf32 <= 0x06F9) ||  // Extended Arabic-Indic digits\n        (utf32 >= 0x07C0 && utf32 <= 0x07C9) ||  // N'Ko digits\n        (utf32 >= 0x0966 && utf32 <= 0x096F) ||  // Devanagari digits\n        (utf32 >= 0x09E6 && utf32 <= 0x09EF) ||  // Bengali digits\n        (utf32 >= 0x0A66 && utf32 <= 0x0A6F) ||  // Gurmukhi digits\n        (utf32 >= 0x0AE6 && utf32 <= 0x0AEF) ||  // Gujarati digits\n        (utf32 >= 0x0B66 && utf32 <= 0x0B6F) ||  // Oriya digits\n        (utf32 >= 0x0BE6 && utf32 <= 0x0BEF) ||  // Tamil digits\n        (utf32 >= 0x0C66 && utf32 <= 0x0C6F) ||  // Telugu digits\n        (utf32 >= 0x0CE6 && utf32 <= 0x0CEF) ||  // Kannada digits\n        (utf32 >= 0x0D66 && utf32 <= 0x0D6F) ||  // Malayalam digits\n        (utf32 >= 0x0E50 && utf32 <= 0x0E59) ||  // Thai digits\n        (utf32 >= 0x0ED0 && utf32 <= 0x0ED9) ||  // Lao digits\n        (utf32 >= 0x0F20 && utf32 <= 0x0F29) ||  // Tibetan digits\n        (utf32 >= 0x1040 && utf32 <= 0x1049) ||  // Myanmar digits\n        (utf32 >= 0x17E0 && utf32 <= 0x17E9) ||  // Khmer digits\n        (utf32 >= 0x1810 && utf32 <= 0x1819) ||  // Mongolian digits\n        (utf32 >= 0xFF10 && utf32 <= 0xFF19);    // Fullwidth digits\n}\n\nstatic bool ui_edit_str_is_alphanumeric(uint32_t utf32) {\n    return ui_edit_str.is_letter(utf32) || ui_edit_str.is_digit(utf32);\n}\n\nstatic bool ui_edit_str_is_cjk_or_emoji(uint32_t utf32) {\n    return !ui_edit_str_is_letter(utf32) &&\n       ((utf32 >=  0x4E00 && utf32 <=  0x9FFF) || // CJK Unified Ideographs\n        (utf32 >=  0x3400 && utf32 <=  0x4DBF) || // CJK Unified Ideographs Extension A\n        (utf32 >= 0x20000 && utf32 <= 0x2A6DF) || // CJK Unified Ideographs Extension B\n        (utf32 >= 0x2A700 && utf32 <= 0x2B73F) || // CJK Unified Ideographs Extension C\n        (utf32 >= 0x2B740 && utf32 <= 0x2B81F) || // CJK Unified Ideographs Extension D\n        (utf32 >= 0x2B820 && utf32 <= 0x2CEAF) || // CJK Unified Ideographs Extension E\n        (utf32 >= 0x2CEB0 && utf32 <= 0x2EBEF) || // CJK Unified Ideographs Extension F\n        (utf32 >=  0xF900 && utf32 <=  0xFAFF) || // CJK Compatibility Ideographs\n        (utf32 >= 0x2F800 && utf32 <= 0x2FA1F) || // CJK Compatibility Ideographs Supplement\n        (utf32 >= 0x1F600 && utf32 <= 0x1F64F) || // Emoticons\n        (utf32 >= 0x1F300 && utf32 <= 0x1F5FF) || // Misc Symbols and Pictographs\n        (utf32 >= 0x1F680 && utf32 <= 0x1F6FF) || // Transport and Map\n        (utf32 >= 0x1F700 && utf32 <= 0x1F77F) || // Alchemical Symbols\n        (utf32 >= 0x1F780 && utf32 <= 0x1F7FF) || // Geometric Shapes Extended\n        (utf32 >= 0x1F800 && utf32 <= 0x1F8FF) || // Supplemental Arrows-C\n        (utf32 >= 0x1F900 && utf32 <= 0x1F9FF) || // Supplemental Symbols and Pictographs\n        (utf32 >= 0x1FA00 && utf32 <= 0x1FA6F) || // Chess Symbols\n        (utf32 >= 0x1FA70 && utf32 <= 0x1FAFF) || // Symbols and Pictographs Extended-A\n        (utf32 >= 0x1FB00 && utf32 <= 0x1FBFF));  // Symbols for Legacy Computing\n}\n\nstatic bool ui_edit_str_can_break(uint32_t cp1, uint32_t cp2) {\n    return !ui_edit_str.is_zwj(cp2) &&\n       (ui_edit_str.is_cjk_or_emoji(cp1) || ui_edit_str.is_cjk_or_emoji(cp2) ||\n        ui_edit_str.is_punctuation(cp1)  || ui_edit_str.is_punctuation(cp2)  ||\n        ui_edit_str.is_blank(cp1)        || ui_edit_str.is_blank(cp2)        ||\n        ui_edit_str.is_combining(cp1)    || ui_edit_str.is_combining(cp2)    ||\n        ui_edit_str.is_spacing(cp1)      || ui_edit_str.is_spacing(cp2));\n}\n\n#pragma push_macro(\"ui_edit_usd\")\n#pragma push_macro(\"ui_edit_gbp\")\n#pragma push_macro(\"ui_edit_euro\")\n#pragma push_macro(\"ui_edit_money_bag\")\n#pragma push_macro(\"ui_edit_pot_of_honey\")\n#pragma push_macro(\"ui_edit_gothic_hwair\")\n\n#define ui_edit_usd             \"\\x24\"\n#define ui_edit_gbp             \"\\xC2\\xA3\"\n#define ui_edit_euro            \"\\xE2\\x82\\xAC\"\n// https://www.compart.com/en/unicode/U+1F4B0\n#define ui_edit_money_bag       \"\\xF0\\x9F\\x92\\xB0\"\n// https://www.compart.com/en/unicode/U+1F36F\n#define ui_edit_pot_of_honey    \"\\xF0\\x9F\\x8D\\xAF\"\n// https://www.compart.com/en/unicode/U+10348\n#define ui_edit_gothic_hwair    \"\\xF0\\x90\\x8D\\x88\" // Gothic Letter Hwair\n\nstatic void ui_edit_str_test_replace(void) { // exhaustive permutations\n    // Exhaustive 9,765,625 replace permutations may take\n    // up to 5 minutes of CPU time in release.\n    // Recommended to be invoked at least once after making any\n    // changes to ui_edit_str.replace and around.\n    // Menu: Debug / Windows / Show Diagnostic Tools allows to watch\n    //       memory pressure for whole 3 minutes making sure code is\n    //       not leaking memory profusely.\n    const char* gs[] = { // glyphs\n        \"\", ui_edit_usd, ui_edit_gbp, ui_edit_euro, ui_edit_money_bag\n    };\n    const int32_t gb[] = {0, 1, 2, 3, 4}; // number of bytes per codepoint\n    enum { n = rt_countof(gs) };\n    int32_t npn = 1; // n to the power of n\n    for (int32_t i = 0; i < n; i++) { npn *= n; }\n    int32_t gix_src[n] = {0};\n    // 5^5 = 3,125   3,125 * 3,125 = 9,765,625\n    for (int32_t i = 0; i < npn; i++) {\n        int32_t vi = i;\n        for (int32_t j = 0; j < n; j++) {\n            gix_src[j] = vi % n;\n            vi /= n;\n        }\n        int32_t g2p[n + 1] = {0};\n        int32_t ngx = 1; // next glyph index\n        char src[128] = {0};\n        for (int32_t j = 0; j < n; j++) {\n            if (gix_src[j] > 0) {\n                strcat(src, gs[gix_src[j]]);\n                rt_assert(1 <= ngx && ngx <= n);\n                g2p[ngx] = g2p[ngx - 1] + gb[gix_src[j]];\n                ngx++;\n            }\n        }\n        if (i % 100 == 99) {\n            rt_println(\"%2d%% [%d][%d][%d][%d][%d] \"\n                    \"\\\"%s\\\",\\\"%s\\\",\\\"%s\\\",\\\"%s\\\",\\\"%s\\\": \\\"%s\\\"\",\n                (i * 100) / npn,\n                gix_src[0], gix_src[1], gix_src[2], gix_src[3], gix_src[4],\n                gs[gix_src[0]], gs[gix_src[1]], gs[gix_src[2]],\n                gs[gix_src[3]], gs[gix_src[4]], src);\n        }\n        ui_edit_str_t s = {0};\n        // reference constructor does not copy to heap:\n        bool ok = ui_edit_str_init(&s, src, -1, false);\n        rt_swear(ok);\n        for (int32_t f = 0; f <= s.g; f++) { // from\n            for (int32_t t = f; t <= s.g; t++) { // to\n                int32_t gix_rep[n] = {0};\n                // replace range [f, t] with all possible glyphs sequences:\n                for (int32_t k = 0; k < npn; k++) {\n                    int32_t vk = i;\n                    for (int32_t j = 0; j < n; j++) {\n                        gix_rep[j] = vk % n;\n                        vk /= n;\n                    }\n                    char rep[128] = {0};\n                    for (int32_t j = 0; j < n; j++) { strcat(rep, gs[gix_rep[j]]); }\n                    char e1[128] = {0}; // expected based on s.g2b[]\n                    snprintf(e1, rt_countof(e1), \"%.*s%s%.*s\",\n                        s.g2b[f], src,\n                        rep,\n                        s.b - s.g2b[t], src + s.g2b[t]\n                    );\n                    char e2[128] = {0}; // expected based on gs[]\n                    snprintf(e2, rt_countof(e1), \"%.*s%s%.*s\",\n                        g2p[f], src,\n                        rep,\n                        (int32_t)strlen(src) - g2p[t], src + g2p[t]\n                    );\n                    rt_swear(strcmp(e1, e2) == 0,\n                        \"s.u[%d:%d]: \\\"%.*s\\\" g:%d [%d:%d] rep=\\\"%s\\\" \"\n                        \"e1: \\\"%s\\\" e2: \\\"%s\\\"\",\n                        s.b, s.c, s.b, s.u, s.g, f, t, rep, e1, e2);\n                    ui_edit_str_t c = {0}; // copy\n                    ok = ui_edit_str_init(&c, src, -1, true);\n                    rt_swear(ok);\n                    ok = ui_edit_str_replace(&c, f, t, rep, -1);\n                    rt_swear(ok);\n                    rt_swear(memcmp(c.u, e1, c.b) == 0,\n                           \"s.u[%d:%d]: \\\"%.*s\\\" g:%d [%d:%d] rep=\\\"%s\\\" \"\n                           \"expected: \\\"%s\\\"\",\n                           s.b, s.c, s.b, s.u, s.g,\n                           f, t, rep, e1);\n                    ui_edit_str_free(&c);\n                }\n            }\n        }\n        ui_edit_str_free(&s);\n    }\n}\n\nstatic void ui_edit_str_test_glyph_bytes(void) {\n    #pragma push_macro(\"glyph_bytes_test\")\n    #define glyph_bytes_test(s, b, expectancy) \\\n        rt_swear(rt_str.utf8bytes(s, b) == expectancy)\n    // Valid Sequences\n    glyph_bytes_test(\"a\", 1, 1);\n    glyph_bytes_test(ui_edit_gbp, 2, 2);\n    glyph_bytes_test(ui_edit_euro, 3, 3);\n    glyph_bytes_test(ui_edit_gothic_hwair, 4, 4);\n    // Invalid Continuation Bytes\n    glyph_bytes_test(\"\\xC2\\x00\", 2, 0);\n    glyph_bytes_test(\"\\xE0\\x80\\x00\", 3, 0);\n    glyph_bytes_test(\"\\xF0\\x80\\x80\\x00\", 4, 0);\n    // Overlong Encodings\n    glyph_bytes_test(\"\\xC0\\xAF\", 2, 0); // '!'\n    glyph_bytes_test(\"\\xE0\\x9F\\xBF\", 3, 0); // upside down '?'\n    glyph_bytes_test(\"\\xF0\\x80\\x80\\xBF\", 4, 0); // '~'\n    // UTF-16 Surrogates\n    glyph_bytes_test(\"\\xED\\xA0\\x80\", 3, 0); // High surrogate\n    glyph_bytes_test(\"\\xED\\xBF\\xBF\", 3, 0); // Low surrogate\n    // Code Points Outside Valid Range\n    glyph_bytes_test(\"\\xF4\\x90\\x80\\x80\", 4, 0); // U+110000\n    // Invalid Initial Bytes\n    glyph_bytes_test(\"\\xC0\", 1, 0);\n    glyph_bytes_test(\"\\xC1\", 1, 0);\n    glyph_bytes_test(\"\\xF5\", 1, 0);\n    glyph_bytes_test(\"\\xFF\", 1, 0);\n    // 5-byte sequence (always invalid)\n    glyph_bytes_test(\"\\xF8\\x88\\x80\\x80\\x80\", 5, 0);\n    #pragma pop_macro(\"glyph_bytes_test\")\n}\n\nstatic void ui_edit_str_test(void) {\n    ui_edit_str_test_glyph_bytes();\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"hello\", -1, false);\n        rt_swear(ok);\n        rt_swear(s.b == 5 && s.c == 0 && memcmp(s.u, \"hello\", 5) == 0);\n        rt_swear(s.g == 5 && s.g2b != null);\n        for (int32_t i = 0; i <= s.g; i++) {\n            rt_swear(s.g2b[i] == i);\n        }\n        ui_edit_str_free(&s);\n    }\n    const char* currencies = ui_edit_usd  ui_edit_gbp\n                             ui_edit_euro ui_edit_money_bag;\n    const char* money = currencies;\n    {\n        ui_edit_str_t s = {0};\n        const int32_t n = (int32_t)strlen(currencies);\n        bool ok = ui_edit_str_init(&s, money, n, true);\n        rt_swear(ok);\n        rt_swear(s.b == n && s.c == s.b && memcmp(s.u, money, s.b) == 0);\n        rt_swear(s.g == 4 && s.g2b != null);\n        const int32_t g2b[] = {0, 1, 3, 6, 10};\n        for (int32_t i = 0; i <= s.g; i++) {\n            rt_swear(s.g2b[i] == g2b[i]);\n        }\n        ui_edit_str_free(&s);\n    }\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"hello\", -1, false);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 1, 4, null, 0);\n        rt_swear(ok);\n        rt_swear(s.b == 2 && memcmp(s.u, \"ho\", 2) == 0);\n        rt_swear(s.g == 2 && s.g2b[0] == 0 && s.g2b[1] == 1 && s.g2b[2] == 2);\n        ui_edit_str_free(&s);\n    }\n    {\n        ui_edit_str_t s = {0};\n        bool ok = ui_edit_str_init(&s, \"Hello world\", -1, false);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 5, 6, \" cruel \", -1);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, 0, 5, \"Goodbye\", -1);\n        rt_swear(ok);\n        ok = ui_edit_str_replace(&s, s.g - 5, s.g, \"Universe\", -1);\n        rt_swear(ok);\n        rt_swear(s.g == 22 && s.g2b[0] == 0 && s.g2b[s.g] == s.b);\n        for (int32_t i = 1; i < s.g; i++) {\n            rt_swear(s.g2b[i] == i); // because every glyph is ASCII\n        }\n        rt_swear(memcmp(s.u, \"Goodbye cruel Universe\", 22) == 0);\n        ui_edit_str_free(&s);\n    }\n    #ifdef UI_STR_TEST_REPLACE_ALL_PERMUTATIONS\n        ui_edit_str_test_replace();\n    #else\n        (void)(void*)ui_edit_str_test_replace; // mitigate unused warning\n    #endif\n}\n\n#pragma push_macro(\"ui_edit_gothic_hwair\")\n#pragma push_macro(\"ui_edit_pot_of_honey\")\n#pragma push_macro(\"ui_edit_money_bag\")\n#pragma push_macro(\"ui_edit_euro\")\n#pragma push_macro(\"ui_edit_gbp\")\n#pragma push_macro(\"ui_edit_usd\")\n\n#pragma pop_macro(\"ui_edit_str_parameters\")\n#pragma pop_macro(\"ui_edit_str_check_empty\")\n#pragma pop_macro(\"ui_edit_check_zeros\")\n#pragma pop_macro(\"ui_edit_str_check_from_to\")\n#pragma pop_macro(\"ui_edit_str_check\")\n\n#ifdef UI_EDIT_STR_TEST\n    rt_static_init(ui_edit_str) { ui_edit_str.test(); }\n#endif\n\n// tests:\n\nstatic void ui_edit_doc_test_big_text(void) {\n    enum { MB10 = 10 * 1000 * 1000 };\n    char* text = null;\n    rt_heap.alloc(&text, MB10);\n    memset(text, 'a', (size_t)MB10 - 1);\n    char* p = text;\n    uint32_t seed = 0x1;\n    for (;;) {\n        int32_t n = rt_num.random32(&seed) % 40 + 40;\n        if (p + n >= text + MB10) { break; }\n        p += n;\n        *p = '\\n';\n    }\n    text[MB10 - 1] = 0x00;\n    ui_edit_text_t t = {0};\n    bool ok = ui_edit_text.init(&t, text, MB10, false);\n    rt_swear(ok);\n    ui_edit_text.dispose(&t);\n    rt_heap.free(text);\n}\n\nstatic void ui_edit_doc_test_paragraphs(void) {\n    // ui_edit_doc_to_paragraphs() is about 1 microsecond\n    for (int i = 0; i < 100; i++)\n    {\n        {   // empty string to paragraphs:\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, null, 0, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 1);\n            rt_swear(t.ps[0].u[0] == 0 &&\n                  t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 0 &&\n                  t.ps[0].g == 0);\n            ui_edit_text.dispose(&t);\n        }\n        {   // string without \"\\n\"\n            const char* hello = \"hello\";\n            const int32_t n = (int32_t)strlen(hello);\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, n, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 1);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == n);\n            rt_swear(t.ps[0].g == n);\n            ui_edit_text.dispose(&t);\n        }\n        {   // string with \"\\n\" at the end\n            const char* hello = \"hello\\n\";\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, -1, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 2);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 5);\n            rt_swear(t.ps[0].g == 5);\n            rt_swear(t.ps[1].u[0] == 0x00);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[1].b == 0);\n            rt_swear(t.ps[1].g == 0);\n            ui_edit_text.dispose(&t);\n        }\n        {   // two string separated by \"\\n\"\n            const char* hello = \"hello\\nworld\";\n            const char* world = hello + 6;\n            ui_edit_text_t t = {0};\n            bool ok = ui_edit_text.init(&t, hello, -1, false);\n            rt_swear(ok);\n            rt_swear(t.ps != null && t.np == 2);\n            rt_swear(t.ps[0].u == hello);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[0].b == 5);\n            rt_swear(t.ps[0].g == 5);\n            rt_swear(t.ps[1].u == world);\n            rt_swear(t.ps[0].c == 0);\n            rt_swear(t.ps[1].b == 5);\n            rt_swear(t.ps[1].g == 5);\n            ui_edit_text.dispose(&t);\n        }\n    }\n    for (int i = 0; i < 10; i++) {\n        ui_edit_doc_test_big_text();\n    }\n}\n\ntypedef struct ui_edit_doc_test_notify_s {\n    ui_edit_notify_t notify;\n    int32_t count_before;\n    int32_t count_after;\n} ui_edit_doc_test_notify_t;\n\nstatic void ui_edit_doc_test_before(ui_edit_notify_t* n,\n        const ui_edit_notify_info_t* rt_unused(ni)) {\n    ui_edit_doc_test_notify_t* notify = (ui_edit_doc_test_notify_t*)n;\n    notify->count_before++;\n}\n\nstatic void ui_edit_doc_test_after(ui_edit_notify_t* n,\n        const ui_edit_notify_info_t* rt_unused(ni)) {\n    ui_edit_doc_test_notify_t* notify = (ui_edit_doc_test_notify_t*)n;\n    notify->count_after++;\n}\n\nstatic struct {\n    ui_edit_notify_t notify;\n} ui_edit_doc_test_notify;\n\n\nstatic void ui_edit_doc_test_0(void) {\n    ui_edit_doc_t edit_doc = {0};\n    ui_edit_doc_t* d = &edit_doc;\n    rt_swear(ui_edit_doc.init(d, null, 0, false));\n    ui_edit_text_t ins_text = {0};\n    rt_swear(ui_edit_text.init(&ins_text, \"a\", 1, false));\n    ui_edit_to_do_t undo = {0};\n    rt_swear(ui_edit_text.replace(&d->text, null, &ins_text, &undo));\n    ui_edit_doc.dispose_to_do(&undo);\n    ui_edit_text.dispose(&ins_text);\n    ui_edit_doc.dispose(d);\n}\n\nstatic void ui_edit_doc_test_1(void) {\n    ui_edit_doc_t edit_doc = {0};\n    ui_edit_doc_t* d = &edit_doc;\n    rt_swear(ui_edit_doc.init(d, null, 0, false));\n    ui_edit_text_t ins_text = {0};\n    rt_swear(ui_edit_text.init(&ins_text, \"a\", 1, false));\n    ui_edit_to_do_t undo = {0};\n    rt_swear(ui_edit_text.replace(&d->text, null, &ins_text, &undo));\n    ui_edit_doc.dispose_to_do(&undo);\n    ui_edit_text.dispose(&ins_text);\n    ui_edit_doc.dispose(d);\n}\n\nstatic void ui_edit_doc_test_2(void) {\n    {   // two string separated by \"\\n\"\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        ui_edit_notify_t notify1 = {0};\n        ui_edit_notify_t notify2 = {0};\n        ui_edit_doc_test_notify_t before_and_after = {0};\n        before_and_after.notify.before = ui_edit_doc_test_before;\n        before_and_after.notify.after  = ui_edit_doc_test_after;\n        ui_edit_doc.subscribe(d, &notify1);\n        ui_edit_doc.subscribe(d, &before_and_after.notify);\n        ui_edit_doc.subscribe(d, &notify2);\n        rt_swear(ui_edit_doc.bytes(d, null) == 0, \"expected empty\");\n        const char* hello = \"hello\\nworld\";\n        rt_swear(ui_edit_doc.replace(d, null, hello, -1));\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        rt_swear(t.np == 2);\n        rt_swear(t.ps[0].b == 5);\n        rt_swear(t.ps[0].g == 5);\n        rt_swear(memcmp(t.ps[0].u, \"hello\", 5) == 0);\n        rt_swear(t.ps[1].b == 5);\n        rt_swear(t.ps[1].g == 5);\n        rt_swear(memcmp(t.ps[1].u, \"world\", 5) == 0);\n        ui_edit_text.dispose(&t);\n        ui_edit_doc.unsubscribe(d, &notify1);\n        ui_edit_doc.unsubscribe(d, &before_and_after.notify);\n        ui_edit_doc.unsubscribe(d, &notify2);\n        ui_edit_doc.dispose(d);\n    }\n    // TODO: \"GoodbyeCruelUniverse\" insert 2x\"\\n\" splitting in 3 paragraphs\n    {   // three string separated by \"\\n\"\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        const char* s = \"Goodbye\" \"\\n\" \"Cruel\" \"\\n\" \"Universe\";\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        ui_edit_text.dispose(&t);\n        ui_edit_range_t r = { .from = {.pn = 0, .gp = 4},\n                              .to   = {.pn = 2, .gp = 3} };\n        rt_swear(ui_edit_doc.replace(d, &r, null, 0));\n        rt_swear(d->text.np == 1);\n        rt_swear(d->text.ps[0].b == 9);\n        rt_swear(d->text.ps[0].g == 9);\n        rt_swear(memcmp(d->text.ps[0].u, \"Goodverse\", 9) == 0);\n        rt_swear(ui_edit_doc.replace(d, null, null, 0)); // remove all\n        rt_swear(d->text.np == 1);\n        rt_swear(d->text.ps[0].b == 0);\n        rt_swear(d->text.ps[0].g == 0);\n        ui_edit_doc.dispose(d);\n    }\n    // TODO: \"GoodbyeCruelUniverse\" insert 2x\"\\n\" splitting in 3 paragraphs\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        const char* ins[] = { \"X\\nY\", \"X\\n\", \"\\nY\", \"\\n\", \"X\\nY\\nZ\" };\n        for (int32_t i = 0; i < rt_countof(ins); i++) {\n            rt_swear(ui_edit_doc.init(d, null, 0, false));\n            const char* s = \"GoodbyeCruelUniverse\";\n            rt_swear(ui_edit_doc.replace(d, null, s, -1));\n            ui_edit_range_t r = { .from = {.pn = 0, .gp =  7},\n                                  .to   = {.pn = 0, .gp = 12} };\n            ui_edit_text_t ins_text = {0};\n            ui_edit_text.init(&ins_text, ins[i], -1, false);\n            ui_edit_to_do_t undo = {0};\n            rt_swear(ui_edit_text.replace(&d->text, &r, &ins_text, &undo));\n            ui_edit_to_do_t redo = {0};\n            rt_swear(ui_edit_text.replace(&d->text, &undo.range, &undo.text, &redo));\n            ui_edit_doc.dispose_to_do(&undo);\n            undo.range = (ui_edit_range_t){0};\n            rt_swear(ui_edit_text.replace(&d->text, &redo.range, &redo.text, &undo));\n            ui_edit_doc.dispose_to_do(&redo);\n            ui_edit_doc.dispose_to_do(&undo);\n            ui_edit_text.dispose(&ins_text);\n            ui_edit_doc.dispose(d);\n        }\n    }\n}\n\nstatic void ui_edit_doc_test_3(void) {\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        ui_edit_doc_test_notify_t before_and_after = {0};\n        before_and_after.notify.before = ui_edit_doc_test_before;\n        before_and_after.notify.after  = ui_edit_doc_test_after;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        rt_swear(ui_edit_doc.subscribe(d, &before_and_after.notify));\n        const char* s = \"Goodbye Cruel Universe\";\n        const int32_t before = before_and_after.count_before;\n        const int32_t after  = before_and_after.count_after;\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        const int32_t bytes = (int32_t)strlen(s);\n        rt_swear(before + 1 == before_and_after.count_before);\n        rt_swear(after  + 1 == before_and_after.count_after);\n        rt_swear(d->text.np == 1);\n        rt_swear(ui_edit_doc.bytes(d, null) == bytes);\n        ui_edit_text_t t = {0};\n        rt_swear(ui_edit_doc.copy_text(d, null, &t));\n        rt_swear(t.np == 1);\n        rt_swear(t.ps[0].b == bytes);\n        rt_swear(t.ps[0].g == bytes);\n        rt_swear(memcmp(t.ps[0].u, s, t.ps[0].b) == 0);\n        // with \"\\n\" and 0x00 at the end:\n        int32_t utf8bytes = ui_edit_doc.utf8bytes(d, null);\n        char* p = null;\n        rt_swear(rt_heap.alloc((void**)&p, utf8bytes) == 0);\n        p[utf8bytes - 1] = 0xFF;\n        ui_edit_doc.copy(d, null, p, utf8bytes);\n        rt_swear(p[utf8bytes - 1] == 0x00);\n        rt_swear(memcmp(p, s, bytes) == 0);\n        rt_heap.free(p);\n        ui_edit_text.dispose(&t);\n        ui_edit_doc.unsubscribe(d, &before_and_after.notify);\n        ui_edit_doc.dispose(d);\n    }\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        const char* s =\n            \"Hello World\"\n            \"\\n\"\n            \"Goodbye Cruel Universe\";\n        rt_swear(ui_edit_doc.replace(d, null, s, -1));\n        rt_swear(ui_edit_doc.undo(d));\n        rt_swear(ui_edit_doc.bytes(d, null) == 0);\n        rt_swear(ui_edit_doc.utf8bytes(d, null) == 1);\n        rt_swear(ui_edit_doc.redo(d));\n        {\n            int32_t utf8bytes = ui_edit_doc.utf8bytes(d, null);\n            char* p = null;\n            rt_swear(rt_heap.alloc((void**)&p, utf8bytes) == 0);\n            p[utf8bytes - 1] = 0xFF;\n            ui_edit_doc.copy(d, null, p, utf8bytes);\n            rt_swear(p[utf8bytes - 1] == 0x00);\n            rt_swear(memcmp(p, s, utf8bytes) == 0);\n            rt_heap.free(p);\n        }\n        ui_edit_doc.dispose(d);\n    }\n}\n\nstatic void ui_edit_doc_test_4(void) {\n    {\n        ui_edit_doc_t edit_doc = {0};\n        ui_edit_doc_t* d = &edit_doc;\n        rt_swear(ui_edit_doc.init(d, null, 0, false));\n        ui_edit_range_t r = {0};\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"a\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"b\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"c\", -1));\n        r = ui_edit_text.end_range(&d->text);\n        rt_swear(ui_edit_doc.replace(d, &r, \"\\n\", -1));\n        ui_edit_doc.dispose(d);\n    }\n}\n\nstatic void ui_edit_doc_test(void) {\n    {\n        ui_edit_range_t r = { .from = {0,0}, .to = {0,0} };\n        rt_static_assertion(sizeof(r.from) + sizeof(r.from) == sizeof(r.a));\n        rt_swear(&r.from == &r.a[0] && &r.to == &r.a[1]);\n    }\n    #ifdef UI_EDIT_DOC_TEST_PARAGRAPHS\n        ui_edit_doc_test_paragraphs();\n    #else\n        (void)(void*)ui_edit_doc_test_paragraphs; // unused\n    #endif\n    // use n = 10,000,000 and Diagnostic Tools to watch for memory leaks\n    enum { n = 1000 };\n//  enum { n = 10 * 1000 * 1000 };\n    for (int32_t i = 0; i < n; i++) {\n        ui_edit_doc_test_0();\n        ui_edit_doc_test_1();\n        ui_edit_doc_test_2();\n        ui_edit_doc_test_3();\n        ui_edit_doc_test_4();\n    }\n}\n\nstatic const ui_edit_range_t ui_edit_invalid_range = {\n    .from = { .pn = -1, .gp = -1},\n    .to   = { .pn = -1, .gp = -1}\n};\n\nui_edit_range_if ui_edit_range = {\n    .compare       = ui_edit_range_compare,\n    .order         = ui_edit_range_order,\n    .is_valid      = ui_edit_range_is_valid,\n    .is_empty      = ui_edit_range_is_empty,\n    .uint64        = ui_edit_range_uint64,\n    .pg            = ui_edit_range_pg,\n    .inside        = ui_edit_range_inside_text,\n    .intersect     = ui_edit_range_intersect,\n    .invalid_range = &ui_edit_invalid_range\n};\n\nui_edit_text_if ui_edit_text = {\n    .init          = ui_edit_text_init,\n    .bytes         = ui_edit_text_bytes,\n    .all_on_null   = ui_edit_text_all_on_null,\n    .ordered       = ui_edit_text_ordered,\n    .end           = ui_edit_text_end,\n    .end_range     = ui_edit_text_end_range,\n    .dup           = ui_edit_text_dup,\n    .equal         = ui_edit_text_equal,\n    .copy_text     = ui_edit_text_copy_text,\n    .copy          = ui_edit_text_copy,\n    .replace       = ui_edit_text_replace,\n    .replace_utf8  = ui_edit_text_replace_utf8,\n    .dispose       = ui_edit_text_dispose\n};\n\nui_edit_doc_if ui_edit_doc = {\n    .init               = ui_edit_doc_init,\n    .replace            = ui_edit_doc_replace,\n    .bytes              = ui_edit_doc_bytes,\n    .copy_text          = ui_edit_doc_copy_text,\n    .utf8bytes          = ui_edit_doc_utf8bytes,\n    .copy               = ui_edit_doc_copy,\n    .redo               = ui_edit_doc_redo,\n    .undo               = ui_edit_doc_undo,\n    .subscribe          = ui_edit_doc_subscribe,\n    .unsubscribe        = ui_edit_doc_unsubscribe,\n    .dispose_to_do      = ui_edit_doc_dispose_to_do,\n    .dispose            = ui_edit_doc_dispose,\n    .test               = ui_edit_doc_test\n};\n\n#pragma push_macro(\"ui_edit_doc_dump\")\n#pragma push_macro(\"ui_edit_text_dump\")\n#pragma push_macro(\"ui_edit_range_dump\")\n#pragma push_macro(\"ui_edit_pg_dump\")\n#pragma push_macro(\"ui_edit_check_range_inside_text\")\n#pragma push_macro(\"ui_edit_check_pg_inside_text\")\n#pragma push_macro(\"ui_edit_check_zeros\")\n\n#ifdef UI_EDIT_DOC_TEST\n    rt_static_init(ui_edit_doc) { ui_edit_doc.test(); }\n#endif\n\n"
  },
  {
    "path": "src/ui/ui_edit_view.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"ui/ui_edit_doc.h\"\n\n// TODO: find all \"== dt->np\" it is wrong pn < dt->np fix them all\n// TODO: undo/redo coalescing\n// TODO: back/forward navigation\n// TODO: exit (Ctrl+W?)/save(Ctrl+S, Ctrl+Shift+S) keyboard shortcuts?\n// TODO: ctrl left, ctrl right jump word ctrl+shift left/right select word?\n// TODO: iBeam cursor (definitely yes - see how MSVC does it)\n// TODO: vertical scrollbar ui\n// TODO: horizontal scroll: trivial to implement:\n//       add horizontal_scroll to e->w and paint\n//       paragraphs in a horizontally shifted clip\n\n// http://worrydream.com/refs/Tesler%20-%20A%20Personal%20History%20of%20Modeless%20Text%20Editing%20and%20Cut-Copy-Paste.pdf\n// https://web.archive.org/web/20221216044359/http://worrydream.com/refs/Tesler%20-%20A%20Personal%20History%20of%20Modeless%20Text%20Editing%20and%20Cut-Copy-Paste.pdf\n\n// Rich text options that are not addressed yet:\n// * Color of ranges (useful for code editing)\n// * Soft line breaks inside the paragraph (useful for e.g. bullet lists of options)\n// * Bold/Italic/Underline (along with color ranges)\n// * Multiple fonts (as long as run vertical size is the maximum of font)\n// * Kerning (?! like in overhung \"Fl\")\n\n// When implementation and header are amalgamated\n// into a single file header library name_space is\n// used to separate different modules namespaces.\n\ntypedef  struct ui_edit_glyph_s {\n    const char* s;\n    int32_t bytes;\n} ui_edit_glyph_t;\n\nstatic void ui_edit_layout(ui_view_t* v);\nstatic ui_point_t ui_edit_pg_to_xy(ui_edit_view_t* e, const ui_edit_pg_t pg);\n\n// Glyphs in monospaced Windows fonts may have different width for non-ASCII\n// characters. Thus even if edit is monospaced glyph measurements are used\n// in text layout.\n\nstatic void ui_edit_invalidate_parent(const ui_edit_view_t* e, const ui_rect_t* rc) {\n    // For transparent background of edit_view parent must draw background.\n    // In the current implementation invalidate() causes whole stack redraw\n    // in rectangle thus it does not matter much. But if it is ever optimized\n    // it will matter.\n    ui_color_t b = e->background;\n    if (ui_color_is_undefined(b) || ui_color_is_transparent(b)) {\n        ui_view.invalidate(e->parent, rc);\n    }\n}\n\nstatic void ui_edit_invalidate_rect(const ui_edit_view_t* e, const ui_rect_t rc) {\n    rt_assert(rc.w >= 0 && rc.h > 0); // w may be zero for empty selection\n    if (rc.w > 0 && rc.h > 0) {\n        ui_view.invalidate(&e->view, &rc);\n        ui_edit_invalidate_parent(e, &rc);\n    }\n}\n\nstatic void ui_edit_invalidate_view(const ui_edit_view_t* e) {\n    ui_view.invalidate(&e->view, null);\n    ui_edit_invalidate_parent(e, null);\n}\n\nstatic int32_t ui_edit_line_height(ui_edit_view_t* e) {\n    // at 96dpi:\n    // \"Segoe UI\" height + line_gap: 16\n    // ui_app.fm.prop h: 15 pt: 11.250 a:  3 c:  9 d: 3 bl: 12 il: 3 lg: 2\n    // \"Cascadia Mono\" height + line_gap: 17\n    // ui_app.fm.mono h: 16 pt: 12.000 a:  2 c: 11 d: 3 bl: 13 il: 4 lg: 0\n    return e->fm->height + e->fm->line_gap;\n}\n\nstatic ui_rect_t ui_edit_selection_rect(ui_edit_view_t* e) {\n    const ui_edit_range_t r = ui_edit_range.order(e->selection);\n    const ui_ltrb_t i = ui_view.margins(&e->view, &e->insets);\n    const ui_point_t p0 = ui_edit_pg_to_xy(e, r.from);\n    const ui_point_t p1 = ui_edit_pg_to_xy(e, r.to);\n    if (p0.x < 0 || p1.x < 0) { // selection outside of visible area\n        return (ui_rect_t) { .x = 0, .y = 0, .w = e->w, .h = e->h };\n    } else if (p0.y == p1.y) {\n        const int32_t max_w = rt_max(e->fm->max_char_width, e->fm->em.w);\n        int32_t w = p1.x - p0.x != 0 ?\n                p1.x - p0.x + max_w : e->caret_width;\n        return (ui_rect_t) { .x = p0.x, .y = i.top + p0.y,\n                             .w = w, .h = ui_edit_line_height(e) };\n    } else {\n        const int32_t h = p1.y - p0.y + ui_edit_line_height(e);\n        return (ui_rect_t) { .x = 0, .y = i.top + p0.y,\n                             .w = e->w, .h = h };\n    }\n}\n\n#if 0\nstatic void ui_edit_text_width_gp(ui_edit_view_t* e, const char* utf8, int32_t bytes) {\n    const int32_t glyphs = rt_str.glyphs(utf8, bytes);\n    rt_println(\"\\\"%.*s\\\" bytes:%d glyphs:%d\", bytes, utf8, bytes, glyphs);\n    int32_t* x = (int32_t*)rt_stackalloc((glyphs + 1) * sizeof(int32_t));\n    const ui_gdi_ta_t ta = { .fm = e->fm };\n    ui_wh_t wh = ui_gdi.glyphs_placement(&ta, utf8,  bytes, x, glyphs);\n//  rt_println(\"wh: %dx%d\", wh.w, wh.h);\n}\n#endif\n\nstatic int32_t ui_edit_text_width(ui_edit_view_t* e, const char* s, int32_t n) {\n//  fp64_t time = rt_clock.seconds();\n    // average GDI measure_text() performance per character:\n    // \"ui_app.fm.mono\"    ~500us (microseconds)\n    // \"ui_app.fm.prop.normal\" ~250us (microseconds) DirectWrite ~100us\n    const ui_gdi_ta_t ta = { .fm = e->fm, .color = e->color,\n                             .measure = true };\n    int32_t x = n == 0 ? 0 : ui_gdi.text(&ta, 0, 0, \"%.*s\", n, s).w;\n//  time = (rt_clock.seconds() - time) * 1000.0;\n//  static fp64_t time_sum;\n//  static fp64_t length_sum;\n//  time_sum += time;\n//  length_sum += n;\n//  rt_println(\"avg=%.6fms per char total %.3fms\", time_sum / length_sum, time_sum);\n    return x;\n}\n\nstatic int32_t ui_edit_word_break_at(ui_edit_view_t* e, int32_t pn, int32_t rn,\n        const int32_t width, bool allow_zero) {\n    // TODO: in sqlite.c 257,674 lines it takes 11 seconds to get all runs()\n    //       on average ui_edit_word_break_at() takes 4 x ui_edit_text_width()\n    //       measurements and they are slow. If we can reduce this amount\n    //       (not clear how) at least 2 times it will be a win.\n    //       Another way is background thread runs() processing but this is\n    //       involving a lot of complexity.\n    //       MSVC devenv.exe edits sqlite3.c w/o any visible delays\n    int32_t count = 0; // stats logging\n    int32_t chars = 0;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    ui_edit_paragraph_t* p = &e->para[pn];\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t k = 1; // at least 1 glyph\n    // offsets inside a run in glyphs and bytes from start of the paragraph:\n    int32_t gp = p->run[rn].gp;\n    int32_t bp = p->run[rn].bp;\n    if (gp < str->g - 1) {\n        const char* text = str->u + bp;\n        const int32_t glyphs_in_this_run = str->g - gp;\n        int32_t* g2b = &str->g2b[gp];\n        // 4 is maximum number of bytes in a UTF-8 sequence\n        int32_t gc = rt_min(4, glyphs_in_this_run);\n        int32_t w = ui_edit_text_width(e, text, g2b[gc] - bp);\n        count++;\n        chars += g2b[gc] - bp;\n        while (gc < glyphs_in_this_run && w < width) {\n            gc = rt_min(gc * 4, glyphs_in_this_run);\n            w = ui_edit_text_width(e, text, g2b[gc] - bp);\n            count++;\n            chars += g2b[gc] - bp;\n        }\n        if (w < width) {\n            k = gc;\n            rt_assert(1 <= k && k <= str->g - gp);\n        } else {\n            int32_t i = 0;\n            int32_t j = gc;\n            k = (i + j) / 2;\n            while (i < j) {\n                rt_assert(allow_zero || 1 <= k && k < gc + 1);\n                const int32_t n = g2b[k + 1] - bp;\n                int32_t px = ui_edit_text_width(e, text, n);\n                count++;\n                chars += n;\n                if (px == width) { break; }\n                if (px < width) { i = k + 1; } else { j = k; }\n                if (!allow_zero && (i + j) / 2 == 0) { break; }\n                k = (i + j) / 2;\n                rt_assert(allow_zero || 1 <= k && k <= str->g - gp);\n            }\n        }\n    }\n    rt_assert(allow_zero || 1 <= k && k <= str->g - gp);\n    return k;\n}\n\nstatic int32_t ui_edit_word_break(ui_edit_view_t* e, int32_t pn, int32_t rn) {\n    return ui_edit_word_break_at(e, pn, rn, e->edit.w, false);\n}\n\nstatic int32_t ui_edit_glyph_at_x(ui_edit_view_t* e, int32_t pn, int32_t rn,\n        int32_t x) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    if (x == 0 || dt->ps[pn].b == 0) {\n        return 0;\n    } else {\n        return ui_edit_word_break_at(e, pn, rn, x + 1, true);\n    }\n}\n\nstatic ui_edit_glyph_t ui_edit_glyph_at(ui_edit_view_t* e, ui_edit_pg_t p) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_glyph_t g = { .s = \"\", .bytes = 0 };\n    rt_assert(0 <= p.pn && p.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[p.pn];\n    const int32_t bytes = str->b;\n    const char* s = str->u;\n    const int32_t bp = str->g2b[p.gp];\n    if (bp < bytes) {\n        g.s = s + bp;\n        g.bytes = rt_str.utf8bytes(g.s, bytes - bp);\n        rt_swear(g.bytes > 0);\n    }\n    return g;\n}\n\n// paragraph_runs() breaks paragraph into `runs` according to `width`\n\nstatic const ui_edit_run_t* ui_edit_paragraph_runs(ui_edit_view_t* e, int32_t pn,\n        int32_t* runs) {\n//  fp64_t time = rt_clock.seconds();\n    rt_assert(e->w > 0);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_run_t* r = null;\n    if (e->para[pn].run != null) {\n        *runs = e->para[pn].runs;\n        r = e->para[pn].run;\n    } else {\n        rt_assert(0 <= pn && pn < dt->np);\n        ui_edit_paragraph_t* p = &e->para[pn];\n        const ui_edit_str_t* str = &dt->ps[pn];\n        if (p->run == null) {\n            rt_assert(p->runs == 0 && p->run == null);\n            const int32_t max_runs = str->b + 1;\n            bool ok = rt_heap.alloc((void**)&p->run, max_runs *\n                                    sizeof(ui_edit_run_t)) == 0;\n            rt_swear(ok);\n            ui_edit_run_t* run = p->run;\n            run[0].bp = 0;\n            run[0].gp = 0;\n            int32_t gc = str->b == 0 ? 0 : ui_edit_word_break(e, pn, 0);\n            if (gc == str->g) { // whole paragraph fits into width\n                p->runs = 1;\n                run[0].bytes  = str->b;\n                run[0].glyphs = str->g;\n                int32_t pixels = ui_edit_text_width(e, str->u, str->g2b[gc]);\n                run[0].pixels = pixels;\n            } else {\n                rt_assert(gc < str->g);\n                int32_t rc = 0; // runs count\n                int32_t ix = 0; // glyph index from to start of paragraph\n                const char* text = str->u;\n                int32_t bytes = str->b;\n                while (bytes > 0) {\n                    rt_assert(rc < max_runs);\n                    run[rc].bp = (int32_t)(text - str->u);\n                    run[rc].gp = ix;\n                    int32_t glyphs = ui_edit_word_break(e, pn, rc);\n                    int32_t utf8bytes = str->g2b[ix + glyphs] - run[rc].bp;\n                    int32_t pixels = ui_edit_text_width(e, text, utf8bytes);\n                    if (glyphs > 1 && utf8bytes < bytes && text[utf8bytes - 1] != 0x20) {\n                        // try to find word break SPACE character. utf8 space is 0x20\n                        int32_t i = utf8bytes;\n                        while (i > 0 && text[i - 1] != 0x20) { i--; }\n                        if (i > 0 && i != utf8bytes) {\n                            utf8bytes = i;\n                            glyphs = rt_str.glyphs(text, utf8bytes);\n                            rt_assert(glyphs >= 0);\n                            pixels = ui_edit_text_width(e, text, utf8bytes);\n                        }\n                    }\n                    run[rc].bytes  = utf8bytes;\n                    run[rc].glyphs = glyphs;\n                    run[rc].pixels = pixels;\n                    rc++;\n                    text += utf8bytes;\n                    rt_assert(0 <= utf8bytes && utf8bytes <= bytes);\n                    bytes -= utf8bytes;\n                    ix += glyphs;\n                }\n                rt_assert(rc > 0);\n                p->runs = rc; // truncate heap capacity array:\n                ok = rt_heap.realloc((void**)&p->run, rc * sizeof(ui_edit_run_t)) == 0;\n                rt_swear(ok);\n            }\n        }\n        *runs = p->runs;\n        r = p->run;\n    }\n    rt_assert(r != null && *runs >= 1);\n    return r;\n}\n\nstatic int32_t ui_edit_paragraph_run_count(ui_edit_view_t* e, int32_t pn) {\n    rt_swear(e->w > 0);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    int32_t runs = 0;\n    if (e->w > 0 && 0 <= pn && pn < dt->np) {\n        (void)ui_edit_paragraph_runs(e, pn, &runs);\n    }\n    return runs;\n}\n\nstatic int32_t ui_edit_glyphs_in_paragraph(ui_edit_view_t* e, int32_t pn) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    (void)ui_edit_paragraph_run_count(e, pn); // word break into runs\n    return dt->ps[pn].g;\n}\n\nstatic void ui_edit_create_caret(ui_edit_view_t* e) {\n    rt_fatal_if(e->focused);\n    rt_assert(ui_app.is_active());\n    rt_assert(ui_app.focused());\n    fp64_t px = ui_app.dpi.monitor_raw / 100.0 + 0.5;\n    e->caret_width = rt_min(3, rt_max(1, (int32_t)px));\n    ui_app.create_caret(e->caret_width, e->fm->height); // w/o line_gap\n    e->focused = true; // means caret was created\n//  rt_println(\"e->focused := true %s\", ui_view_debug_id(&e->view));\n}\n\nstatic void ui_edit_destroy_caret(ui_edit_view_t* e) {\n    rt_fatal_if(!e->focused);\n    ui_app.destroy_caret();\n    e->focused = false; // means caret was destroyed\n//  rt_println(\"e->focused := false %s\", ui_view_debug_id(&e->view));\n}\n\nstatic void ui_edit_show_caret(ui_edit_view_t* e) {\n    if (e->focused) {\n        rt_assert(ui_app.is_active());\n        rt_assert(ui_app.focused());\n        rt_assert((e->caret.x < 0) == (e->caret.y < 0));\n        const ui_ltrb_t insets = ui_view.margins(&e->view, &e->insets);\n        int32_t x = e->caret.x < 0 ? insets.left : e->caret.x;\n        int32_t y = e->caret.y < 0 ? insets.top  : e->caret.y;\n        ui_app.move_caret(e->x + x, e->y + y);\n        // TODO: it is possible to support unblinking caret if desired\n        // do not set blink time - use global default\n//      fatal_if_false(SetCaretBlinkTime(500));\n        ui_app.show_caret();\n        e->shown++;\n        rt_assert(e->shown == 1);\n    }\n}\n\nstatic void ui_edit_hide_caret(ui_edit_view_t* e) {\n    if (e->focused) {\n        ui_app.hide_caret();\n        e->shown--;\n        rt_assert(e->shown == 0);\n    }\n}\n\nstatic void ui_edit_allocate_runs(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(e->para == null);\n    rt_assert(dt->np > 0);\n    rt_assert(e->para == null);\n    bool done = rt_heap.alloc_zero((void**)&e->para,\n                dt->np * sizeof(e->para[0])) == 0;\n    rt_swear(done, \"out of memory - cannot continue\");\n}\n\nstatic void ui_edit_invalidate_run(ui_edit_view_t* e, int32_t i) {\n    if (e->para[i].run != null) {\n        rt_assert(e->para[i].runs > 0);\n        rt_heap.free(e->para[i].run);\n        e->para[i].run = null;\n        e->para[i].runs = 0;\n    } else {\n        rt_assert(e->para[i].runs == 0);\n    }\n}\n\nstatic void ui_edit_invalidate_runs(ui_edit_view_t* e, int32_t f, int32_t t,\n        int32_t np) { // [from..to] inclusive inside [0..np - 1]\n    rt_swear(e->para != null && f <= t && 0 <= f && t < np);\n    for (int32_t i = f; i <= t; i++) { ui_edit_invalidate_run(e, i); }\n}\n\nstatic void ui_edit_invalidate_all_runs(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_invalidate_runs(e, 0, dt->np - 1, dt->np);\n}\n\nstatic void ui_edit_dispose_runs(ui_edit_view_t* e, int32_t np) {\n    rt_assert(e->para != null);\n    ui_edit_invalidate_runs(e, 0, np - 1, np);\n    rt_heap.free(e->para);\n    e->para = null;\n}\n\nstatic void ui_edit_dispose_all_runs(ui_edit_view_t* e) {\n    ui_edit_dispose_runs(e, e->doc->text.np);\n}\n\nstatic void ui_edit_layout_now(ui_edit_view_t* e) {\n    if (e->measure != null && e->layout != null && e->w > 0) {\n        e->layout(&e->view);\n        ui_edit_invalidate_view(e);\n    }\n}\n\nstatic void ui_edit_if_sle_layout(ui_edit_view_t* e) {\n    // only for single line edit controls that were already initialized\n    // and measured horizontally at least once.\n    if (e->sle && e->layout != null && e->w > 0) {\n        ui_edit_layout_now(e);\n    }\n}\n\nstatic void ui_edit_view_set_font(ui_edit_view_t* e, ui_fm_t* f) {\n    ui_edit_invalidate_all_runs(e);\n    e->scroll.rn = 0;\n    e->fm = f;\n    ui_edit_layout_now(e);\n    ui_app.request_layout();\n}\n\n// Paragraph number, glyph number -> run number\n\nstatic ui_edit_pr_t ui_edit_pg_to_pr(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pg.pn];\n    ui_edit_pr_t pr = { .pn = pg.pn, .rn = -1 };\n    if (str->b == 0) { // empty\n        rt_assert(pg.gp == 0);\n        pr.rn = 0;\n    } else {\n        rt_assert(0 <= pg.pn && pg.pn < dt->np);\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pg.pn, &runs);\n        if (pg.gp == str->g + 1) {\n            pr.rn = runs - 1; // TODO: past last glyph ??? is this correct?\n        } else {\n            rt_assert(0 <= pg.gp && pg.gp <= str->g);\n            for (int32_t j = 0; j < runs && pr.rn < 0; j++) {\n                const int32_t last_run = j == runs - 1;\n                const int32_t start = run[j].gp;\n                const int32_t end = run[j].gp + run[j].glyphs + last_run;\n                if (start <= pg.gp && pg.gp < end) {\n                    pr.rn = j;\n                }\n            }\n            rt_assert(pr.rn >= 0);\n        }\n    }\n    return pr;\n}\n\nstatic int32_t ui_edit_runs_between(ui_edit_view_t* e, const ui_edit_pg_t pg0,\n        const ui_edit_pg_t pg1) {\n    rt_assert(ui_edit_range.uint64(pg0) <= ui_edit_range.uint64(pg1));\n    int32_t rn0 = ui_edit_pg_to_pr(e, pg0).rn;\n    int32_t rn1 = ui_edit_pg_to_pr(e, pg1).rn;\n    int32_t rc = 0;\n    if (pg0.pn == pg1.pn) {\n        rt_assert(rn0 <= rn1);\n        rc = rn1 - rn0;\n    } else {\n        rt_assert(pg0.pn < pg1.pn);\n        for (int32_t i = pg0.pn; i < pg1.pn; i++) {\n            const int32_t runs = ui_edit_paragraph_run_count(e, i);\n            if (i == pg0.pn) {\n                rc += runs - rn0;\n            } else { // i < pg1.pn\n                rc += runs;\n            }\n        }\n        rc += rn1;\n    }\n    return rc;\n}\n\nstatic ui_edit_pg_t ui_edit_scroll_pg(ui_edit_view_t* e) {\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, e->scroll.pn, &runs);\n    // layout may decrease number of runs when view is growing:\n    if (e->scroll.rn >= runs) { e->scroll.rn = runs - 1; }\n    rt_assert(0 <= e->scroll.rn && e->scroll.rn < runs,\n            \"e->scroll.rn: %d runs: %d\", e->scroll.rn, runs);\n    return (ui_edit_pg_t) { .pn = e->scroll.pn, .gp = run[e->scroll.rn].gp };\n}\n\nstatic int32_t ui_edit_first_visible_run(ui_edit_view_t* e, int32_t pn) {\n    return pn == e->scroll.pn ? e->scroll.rn : 0;\n}\n\n// ui_edit::pg_to_xy() paragraph # glyph # -> (x,y) in [0,0  width x height]\n\nstatic ui_point_t ui_edit_pg_to_xy(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    ui_point_t pt = { .x = -1, .y = 0 };\n    const int32_t spn = e->scroll.pn + 1;\n    const int32_t pn = rt_min(rt_max(spn, pg.pn + 1), dt->np - 1);\n    for (int32_t i = e->scroll.pn; i <= pn && pt.x < 0; i++) {\n        rt_assert(0 <= i && i < dt->np);\n        const ui_edit_str_t* str = &dt->ps[i];\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, i, &runs);\n        for (int32_t j = ui_edit_first_visible_run(e, i); j < runs; j++) {\n            const int32_t last_run = j == runs - 1;\n            const int32_t gc = run[j].glyphs; // glyphs count\n            if (i == pg.pn) {\n                // in the last `run` of a paragraph x after last glyph is OK\n                if (run[j].gp <= pg.gp && pg.gp < run[j].gp + gc + last_run) {\n                    const char* s = str->u + run[j].bp;\n                    const uint32_t bp2e = str->b - run[j].bp; // to end of str\n                    int32_t ofs = ui_edit_str.gp_to_bp(s, bp2e, pg.gp - run[j].gp);\n                    rt_swear(ofs >= 0);\n                    pt.x = ui_edit_text_width(e, s, ofs);\n                    break;\n                }\n            }\n            pt.y += ui_edit_line_height(e);\n        }\n    }\n    if (0 <= pt.x && pt.x < e->edit.w && 0 <= pt.y && pt.y < e->edit.h) {\n        // all good, inside visible rectangle or right after it\n    } else {\n        rt_println(\"%d:%d (%d,%d) outside of %dx%d\", pg.pn, pg.gp,\n            pt.x, pt.y, e->edit.w, e->edit.h);\n        pt = (ui_point_t){-1, -1};\n    }\n    return pt;\n}\n\nstatic int32_t ui_edit_glyph_width_px(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pg.pn];\n    const char* text = str->u;\n    int32_t gc = str->g;\n    if (pg.gp == 0 &&  gc == 0) {\n        return 0; // empty paragraph\n    } else if (pg.gp < gc) {\n        const int32_t bp = ui_edit_str.gp_to_bp(text, str->b, pg.gp);\n        rt_swear(bp >= 0);\n        const char* s = text + bp;\n        int32_t bytes_in_glyph = rt_str.utf8bytes(s, str->b - bp);\n        rt_swear(bytes_in_glyph > 0);\n        int32_t x = ui_edit_text_width(e, s, bytes_in_glyph);\n        return x;\n    } else {\n        rt_assert(pg.gp == gc, \"only next position past last glyph is allowed\");\n        return 0;\n    }\n}\n\n// xy_to_pg() (x,y) (0,0, width x height) -> paragraph # glyph #\n\nstatic ui_edit_pg_t ui_edit_xy_to_pg(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = {-1, -1};\n    int32_t py = 0; // paragraph `y' coordinate\n    for (int32_t i = e->scroll.pn; i < dt->np && pg.pn < 0; i++) {\n        rt_assert(0 <= i && i < dt->np);\n        const ui_edit_str_t* str = &dt->ps[i];\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, i, &runs);\n        for (int32_t j = ui_edit_first_visible_run(e, i); j < runs && pg.pn < 0; j++) {\n            const ui_edit_run_t* r = &run[j];\n            const char* s = str->u + run[j].bp;\n            if (py <= y && y < py + ui_edit_line_height(e)) {\n                int32_t w = ui_edit_text_width(e, s, r->bytes);\n                pg.pn = i;\n                if (x >= w) {\n                    pg.gp = r->gp + r->glyphs;\n                } else {\n                    pg.gp = r->gp + ui_edit_glyph_at_x(e, i, j, x);\n                    if (pg.gp < r->glyphs - 1) {\n                        ui_edit_pg_t right = {pg.pn, pg.gp + 1};\n                        int32_t x0 = ui_edit_pg_to_xy(e, pg).x;\n                        int32_t x1 = ui_edit_pg_to_xy(e, right).x;\n                        if (x1 - x < x - x0) {\n                            pg.gp++; // snap to closest glyph's 'x'\n                        }\n                    }\n                }\n            } else {\n                py += ui_edit_line_height(e);\n            }\n        }\n        if (py > e->h) { break; }\n    }\n    return pg;\n}\n\nstatic void ui_edit_set_caret(ui_edit_view_t* e, int32_t x, int32_t y) {\n    if (e->caret.x != x || e->caret.y != y) {\n        if (e->focused && ui_app.focused()) {\n            ui_app.move_caret(e->x + x, e->y + y);\n        }\n        const ui_ltrb_t i = ui_view.margins(&e->view, &e->insets);\n        // caret in i.left .. e->view.w - i.right\n        //          i.top  .. e->view.h - i.bottom\n        // coordinate space\n        rt_swear(i.left <= x && x < e->w && i.top <= y && y < e->h);\n        e->caret.x = x;\n        e->caret.y = y;\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_view_end_of_text(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    return (ui_edit_pg_t){ .pn = dt->np - 1, .gp = dt->ps[dt->np - 1].g };\n}\n\nstatic ui_edit_pg_t ui_edit_view_last_fully_visible(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = ui_edit_scroll_pg(e);\n    int32_t visible_runs = e->visible_runs;\n    while (visible_runs > 0) {\n        int32_t runs = 0;\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pg.pn, &runs);\n        int32_t i = 0;\n        pg.gp = 0;\n        while (visible_runs > 0 && i < runs) {\n            pg.gp += run[i].glyphs;\n            visible_runs--;\n            i++;\n        }\n        if (visible_runs > 0) {\n            if (pg.pn < dt->np - 1) {\n                pg.pn++;\n                pg.gp = 0;\n            } else {\n                visible_runs = 0; // reached end of text\n            }\n        }\n    }\n    return pg;\n}\n\n// scroll_up() text moves up (north) in the visible view,\n// scroll position increments moves down (south)\n\nstatic void ui_edit_scroll_up(ui_edit_view_t* e, int32_t run_count) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 < run_count, \"does it make sense to have 0 scroll?\");\n    ui_edit_pg_t eot  = ui_edit_view_end_of_text(e);\n    while (run_count > 0) {\n        ui_edit_pg_t lfv = ui_edit_view_last_fully_visible(e);\n        rt_println(\"eot: %d:%d lfv: %d:%d\", eot.pn, eot.gp, lfv.pn, lfv.gp);\n        if (ui_edit_range.compare(lfv, eot) == 0) {\n            run_count = 0;\n        } else {\n            const int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n            if (e->scroll.rn < runs - 1) {\n                e->scroll.rn++;\n                run_count--;\n            } else if (e->scroll.pn < dt->np - 1) {\n                e->scroll.pn++;\n                e->scroll.rn = 0;\n                run_count--;\n            } else {\n                rt_println(\"???\");\n                run_count = 0; // enough\n            }\n            rt_assert(e->scroll.pn >= 0 && e->scroll.rn >= 0);\n        }\n    }\n    ui_edit_if_sle_layout(e);\n    ui_edit_invalidate_view(e);\n}\n\n// scroll_dw() text moves down (south) in the visible view,\n// scroll position decrements moves up (north)\n\nstatic void ui_edit_scroll_down(ui_edit_view_t* e, int32_t run_count) {\n    rt_assert(0 < run_count, \"does it make sense to have 0 scroll?\");\n    while (run_count > 0 && (e->scroll.pn > 0 || e->scroll.rn > 0)) {\n        int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n        e->scroll.rn = rt_min(e->scroll.rn, runs - 1);\n        if (e->scroll.rn == 0 && e->scroll.pn > 0) {\n            e->scroll.pn--;\n            e->scroll.rn = ui_edit_paragraph_run_count(e, e->scroll.pn) - 1;\n        } else if (e->scroll.rn > 0) {\n            e->scroll.rn--;\n        }\n        rt_assert(e->scroll.pn >= 0 && e->scroll.rn >= 0);\n        rt_assert(0 <= e->scroll.rn &&\n                    e->scroll.rn < ui_edit_paragraph_run_count(e, e->scroll.pn));\n        run_count--;\n    }\n    ui_edit_if_sle_layout(e);\n    ui_edit_invalidate_view(e);\n}\n\nstatic void ui_edit_scroll_into_view(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pg.pn && pg.pn < dt->np && dt->np > 0);\n    if (e->inside.bottom > 0) {\n        if (e->sle) { rt_assert(pg.pn == 0); }\n        const int32_t rn = ui_edit_pg_to_pr(e, pg).rn;\n        const uint64_t scroll = (uint64_t)e->scroll.pn << 32 | e->scroll.rn;\n        const uint64_t caret  = (uint64_t)pg.pn << 32 | rn;\n        uint64_t last = 0;\n        int32_t py = 0;\n        const int32_t pn = e->scroll.pn;\n        const int32_t bottom = e->inside.bottom;\n        for (int32_t i = pn; i < dt->np && py < bottom; i++) {\n            int32_t runs = ui_edit_paragraph_run_count(e, i);\n            const int32_t fvr = ui_edit_first_visible_run(e, i);\n            for (int32_t j = fvr; j < runs && py < bottom; j++) {\n                last = (uint64_t)i << 32 | j;\n                py += ui_edit_line_height(e);\n            }\n        }\n        int32_t sle_runs = e->sle && e->w > 0 ?\n            ui_edit_paragraph_run_count(e, 0) : 0;\n        ui_edit_pg_t end = ui_edit_text.end(dt);\n        ui_edit_pr_t lp = ui_edit_pg_to_pr(e, end);\n        uint64_t eof = (uint64_t)(dt->np - 1) << 32 | lp.rn;\n        if (last == eof && py <= bottom - ui_edit_line_height(e)) {\n            // vertical white space for EOF on the screen\n            last = (uint64_t)dt->np << 32 | 0;\n        }\n        if (scroll <= caret && caret < last) {\n            // no scroll\n        } else if (caret < scroll) {\n            ui_edit_invalidate_view(e);\n            e->scroll.pn = pg.pn;\n            e->scroll.rn = rn;\n        } else if (e->sle && sle_runs * ui_edit_line_height(e) <= e->h) {\n            // single line edit control fits vertically - no scroll\n        } else {\n            ui_edit_invalidate_view(e);\n            rt_assert(caret >= last);\n            e->scroll.pn = pg.pn;\n            e->scroll.rn = rn;\n            while (e->scroll.pn > 0 || e->scroll.rn > 0) {\n                ui_point_t pt = ui_edit_pg_to_xy(e, pg);\n                if (pt.y + ui_edit_line_height(e) > bottom - ui_edit_line_height(e)) { break; }\n                if (e->scroll.rn > 0) {\n                    e->scroll.rn--;\n                } else {\n                    e->scroll.pn--;\n                    e->scroll.rn = ui_edit_paragraph_run_count(e, e->scroll.pn) - 1;\n                }\n            }\n        }\n    }\n}\n\nstatic void ui_edit_caret_to(ui_edit_view_t* e, const ui_edit_pg_t to) {\n    ui_edit_scroll_into_view(e, to);\n    ui_point_t pt =  ui_edit_pg_to_xy(e, to);\n    if (pt.x >= 0 && pt.y >= 0) {\n        ui_edit_set_caret(e, pt.x + e->inside.left, pt.y + e->inside.top);\n    }\n}\n\nstatic void ui_edit_move_caret(ui_edit_view_t* e, const ui_edit_pg_t pg) {\n    if (e->w > 0) { // width == 0 means no measure/layout yet\n        ui_rect_t before = ui_edit_selection_rect(e);\n        ui_edit_text_t* dt = &e->doc->text; // document text\n        rt_assert(0 <= pg.pn && pg.pn < dt->np);\n        // single line edit control cannot move caret past fist paragraph\n        if (!e->sle || pg.pn < dt->np) {\n            e->selection.a[1] = pg;\n            ui_edit_caret_to(e, pg);\n            if (!ui_app.shift && e->edit.buttons == 0) {\n                e->selection.a[0] = e->selection.a[1];\n            }\n        }\n        ui_rect_t after = ui_edit_selection_rect(e);\n        ui_edit_invalidate_rect(e, ui.combine_rect(&before, &after));\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_insert_inline(ui_edit_view_t* e, ui_edit_pg_t pg,\n        const char* text, int32_t bytes) {\n    // insert_inline() inserts text (not containing '\\n' in it)\n    rt_assert(bytes > 0);\n    for (int32_t i = 0; i < bytes; i++) { rt_assert(text[i] != '\\n'); }\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    int32_t g = 0;\n    if (ui_edit_doc.replace(e->doc, &r, text, bytes)) {\n        ui_edit_text_t t = {0};\n        if (ui_edit_text.init(&t, text, bytes, false)) {\n            rt_assert(t.ps != null && t.np == 1);\n            g = t.np == 1 && t.ps != null ? t.ps[0].g : 0;\n            ui_edit_text.dispose(&t);\n        }\n    }\n    r.from.gp += g;\n    r.to.gp += g;\n    e->selection = r;\n    ui_edit_move_caret(e, e->selection.from);\n    return r.to;\n}\n\nstatic ui_edit_pg_t ui_edit_insert_paragraph_break(ui_edit_view_t* e,\n        ui_edit_pg_t pg) {\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    bool ok = ui_edit_doc.replace(e->doc, &r, \"\\n\", 1);\n    ui_edit_pg_t next = {.pn = pg.pn + 1, .gp = 0};\n    return ok ? next : pg;\n}\n\nstatic bool ui_edit_is_blank(ui_edit_glyph_t g) {\n    return g.bytes == 0 || ui_edit_str.is_blank(rt_str.utf32(g.s, g.bytes));\n}\n\nstatic bool ui_edit_is_punctuation(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 && ui_edit_str.is_punctuation(utf32);\n}\n\nstatic bool ui_edit_is_alphanumeric(ui_edit_glyph_t g) {\n    return g.bytes > 0 &&\n        ui_edit_str.is_alphanumeric(rt_str.utf32(g.s, g.bytes));\n}\n\nstatic bool ui_edit_is_cjk_or_emoji_or_symbol(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 &&\n        (ui_edit_str.is_cjk_or_emoji(utf32) || ui_edit_str.is_symbol(utf32));\n}\n\nstatic bool ui_edit_is_break(ui_edit_glyph_t g) {\n    uint32_t utf32 = g.bytes > 0 ? rt_str.utf32(g.s, g.bytes) : 0;\n    return utf32 != 0 &&\n       (ui_edit_str.is_blank(utf32) ||\n        ui_edit_str.is_punctuation(utf32) ||\n        ui_edit_str.is_symbol(utf32) ||\n        ui_edit_str.is_cjk_or_emoji(utf32));\n}\n\nstatic ui_edit_glyph_t ui_edit_left_of(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    if (pg.gp > 0) {\n        pg.gp--;\n        return ui_edit_glyph_at(e, pg);\n    } else {\n        return (ui_edit_glyph_t){ null, 0 };\n    }\n}\n\nstatic ui_edit_glyph_t ui_edit_right_of(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (pg.gp < dt->ps[pg.pn].g - 1) {\n        pg.gp++;\n        return ui_edit_glyph_at(e, pg);\n    } else {\n        return (ui_edit_glyph_t){ null, 0 };\n    }\n}\n\nstatic ui_edit_pg_t ui_edit_skip_left_blanks(ui_edit_view_t* e,\n    ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_swear(pg.pn <= dt->np - 1);\n    while (pg.gp > 0) {\n        pg.gp--;\n        ui_edit_glyph_t glyph = ui_edit_glyph_at(e, pg);\n        if (glyph.bytes > 0 && !ui_edit_is_blank(glyph)) {\n            pg.gp++;\n            break;\n        }\n    }\n    return pg;\n}\n\nstatic ui_edit_pg_t ui_edit_skip_right_blanks(ui_edit_view_t* e,\n    ui_edit_pg_t pg) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_swear(pg.pn <= dt->np - 1);\n    int32_t glyphs = ui_edit_glyphs_in_paragraph(e, pg.pn);\n    ui_edit_glyph_t glyph = ui_edit_glyph_at(e, pg);\n    while (pg.gp < glyphs && glyph.bytes > 0 && ui_edit_is_blank(glyph)) {\n        pg.gp++;\n        glyph = ui_edit_glyph_at(e, pg);\n    }\n    return pg;\n}\n\nstatic ui_edit_range_t ui_edit_word_range(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    ui_edit_range_t r = { .from = pg, .to = pg };\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (0 <= pg.pn && 0 <= pg.gp) {\n        rt_swear(pg.pn <= dt->np - 1);\n        // number of glyphs in paragraph:\n        int32_t ng = ui_edit_glyphs_in_paragraph(e, pg.pn);\n        if (pg.gp > ng) { pg.gp = rt_max(0, ng); }\n        ui_edit_glyph_t g = ui_edit_glyph_at(e, pg);\n        if (ng <= 1) {\n            r.to.gp = ng;\n        } else if (ui_edit_is_cjk_or_emoji_or_symbol(g)) {\n            // r == {pg,pg}\n        } else {\n            ui_edit_pg_t from = pg;\n            ui_edit_pg_t to   = pg;\n            if (pg.gp > 0 && ui_edit_is_punctuation(g)) {\n                from.gp--;\n                g = ui_edit_glyph_at(e, from);\n            } else if (pg.gp > 0 && ui_edit_is_blank(g)) {\n                from.gp--;\n                to.gp--;\n                g = ui_edit_glyph_at(e, from);\n            }\n            if (ui_edit_is_blank(g)) {\n                while (from.gp > 0 &&\n                       ui_edit_is_blank(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_blank(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            } else if (ui_edit_is_alphanumeric(g)) {\n                while (from.gp > 0 &&\n                       ui_edit_is_alphanumeric(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_alphanumeric(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            } else {\n                while (from.gp > 0 &&\n                        ui_edit_is_break(ui_edit_left_of(e, from))) {\n                    from.gp--;\n                }\n                r.from = from;\n                while (to.gp < ng && ui_edit_is_break(g)) {\n                    to.gp++;\n                    g = ui_edit_glyph_at(e, to);\n                }\n                r.to = to;\n            }\n        }\n    }\n    return r;\n}\n\nstatic void ui_edit_ctrl_left(ui_edit_view_t* e) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    const ui_edit_range_t s = e->selection;\n    ui_edit_pg_t to = e->selection.to;\n    if (to.gp == 0) {\n        if (to.pn > 0) {\n            to.pn--;\n            int32_t runs = 0;\n            const ui_edit_run_t* run = ui_edit_paragraph_runs(e, to.pn, &runs);\n            to.gp = run[runs - 1].gp + run[runs - 1].glyphs;\n        }\n    } else {\n        to.gp--;\n    }\n    const ui_edit_pg_t lf = ui_edit_skip_left_blanks(e, to);\n    const ui_edit_range_t w = ui_edit_word_range(e, lf);\n    e->selection.to = w.from;\n    if (ui_app.shift) {\n        e->selection.from = s.from;\n    } else {\n        e->selection.from = w.from;\n    }\n    ui_edit_move_caret(e, e->selection.to);\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n}\n\nstatic void ui_edit_view_key_left(ui_edit_view_t* e) {\n    ui_edit_pg_t to = e->selection.a[1];\n    if (to.pn > 0 || to.gp > 0) {\n        if (ui_app.ctrl) {\n            ui_edit_ctrl_left(e);\n        } else {\n            ui_point_t pt = ui_edit_pg_to_xy(e, to);\n            if (pt.x == 0 && pt.y == 0) {\n                ui_edit_scroll_down(e, 1);\n            }\n            if (to.gp > 0) {\n                to.gp--;\n            } else if (to.pn > 0) {\n                to.pn--;\n                to.gp = ui_edit_glyphs_in_paragraph(e, to.pn);\n            }\n            ui_edit_move_caret(e, to);\n            e->last_x = -1;\n        }\n    }\n}\n\nstatic void ui_edit_ctrl_right(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_range_t s = e->selection;\n    ui_edit_pg_t to = e->selection.to;\n    int32_t glyphs = ui_edit_glyphs_in_paragraph(e, to.pn);\n    if (to.pn < dt->np - 1 || to.gp < glyphs) {\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        if (to.gp == glyphs) {\n            to.pn++;\n            to.gp = 0;\n        } else {\n            to.gp++;\n        }\n        ui_edit_pg_t rt = ui_edit_skip_right_blanks(e, to);\n        ui_edit_range_t w = ui_edit_word_range(e, rt);\n        e->selection.to = w.to;\n        if (ui_app.shift) {\n            e->selection.from = s.from;\n        } else {\n            e->selection.from = w.to;\n        }\n        ui_edit_move_caret(e, e->selection.to);\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    }\n}\n\nstatic void ui_edit_view_key_right(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t to = e->selection.a[1];\n    if (to.pn < dt->np) {\n        if (ui_app.ctrl) {\n            ui_edit_ctrl_right(e);\n        } else {\n            int32_t glyphs = ui_edit_glyphs_in_paragraph(e, to.pn);\n            if (to.gp < glyphs) {\n                to.gp++;\n                ui_edit_scroll_into_view(e, to);\n            } else if (!e->sle && to.pn < dt->np - 1) {\n                to.pn++;\n                to.gp = 0;\n                ui_edit_scroll_into_view(e, to);\n            }\n            ui_edit_move_caret(e, to);\n// TODO: last_x does not work!\n            e->last_x = -1;\n        }\n    }\n}\n\nstatic void ui_edit_reuse_last_x(ui_edit_view_t* e, ui_point_t* pt) {\n    // Vertical caret movement visually tend to move caret horizontally\n    // in proportional font text. Remembering starting `x' value for vertical\n    // movements alleviates this unpleasant UX experience to some degree.\n    if (pt->x > 0) {\n        if (e->last_x > 0) {\n            int32_t prev = e->last_x - e->fm->em.w;\n            int32_t next = e->last_x + e->fm->em.w;\n            if (prev <= pt->x && pt->x <= next) {\n                pt->x = e->last_x;\n            }\n        }\n        e->last_x = pt->x;\n    }\n}\n\nstatic void ui_edit_view_key_up(ui_edit_view_t* e) {\n    const ui_edit_pg_t pg = e->selection.a[1];\n    ui_edit_pg_t to = pg;\n    if (to.pn > 0 || ui_edit_pg_to_pr(e, to).rn > 0) {\n        // top of the text\n        ui_point_t pt = ui_edit_pg_to_xy(e, to);\n        rt_assert(pt.x >= 0 && pt.y >= 0);\n        if (pt.y == 0) {\n            ui_edit_scroll_down(e, 1);\n        } else {\n            pt.y -= 1;\n        }\n        ui_edit_reuse_last_x(e, &pt);\n        rt_assert(pt.y >= 0);\n        to = ui_edit_xy_to_pg(e, pt.x, pt.y);\n        if (to.pn >= 0 && to.gp >= 0) {\n            int32_t rn0 = ui_edit_pg_to_pr(e, pg).rn;\n            int32_t rn1 = ui_edit_pg_to_pr(e, to).rn;\n            if (rn1 > 0 && rn0 == rn1) { // same run\n                rt_assert(to.gp > 0, \"word break must not break on zero gp\");\n                int32_t runs = 0;\n                const ui_edit_run_t* run = ui_edit_paragraph_runs(e, to.pn, &runs);\n                to.gp = run[rn1].gp;\n            }\n        }\n    }\n    if (to.pn >= 0 && to.gp >= 0) {\n        ui_edit_move_caret(e, to);\n    }\n}\n\nstatic void ui_edit_view_key_down(ui_edit_view_t* e) {\n    const ui_edit_pg_t pg = e->selection.a[1];\n    ui_point_t pt = ui_edit_pg_to_xy(e, pg);\n    ui_edit_reuse_last_x(e, &pt); // TODO: does not work! (used to work broken now)\n    // scroll runs guaranteed to be already laid out for current state of view:\n    ui_edit_pg_t scroll = ui_edit_scroll_pg(e);\n    const int32_t run_count = ui_edit_runs_between(e, scroll, pg);\n    if (!e->sle && run_count > e->visible_runs - 1) {\n        ui_edit_scroll_up(e, 1);\n    } else {\n        pt.y += ui_edit_line_height(e);\n    }\n    ui_edit_pg_t to = ui_edit_xy_to_pg(e, pt.x, pt.y);\n    if (to.pn >= 0 && to.gp >= 0) {\n        ui_edit_move_caret(e, to);\n    }\n}\n\nstatic void ui_edit_view_key_home(ui_edit_view_t* e) {\n    if (ui_app.ctrl) {\n        e->scroll.pn = 0;\n        e->scroll.rn = 0;\n        e->selection.a[1].pn = 0;\n        e->selection.a[1].gp = 0;\n        ui_edit_invalidate_view(e);\n    }\n    const int32_t pn = e->selection.a[1].pn;\n    int32_t runs = ui_edit_paragraph_run_count(e, pn);\n    const ui_edit_paragraph_t* para = &e->para[pn];\n    if (runs <= 1) {\n        e->selection.a[1].gp = 0;\n    } else {\n        int32_t rn = ui_edit_pg_to_pr(e, e->selection.a[1]).rn;\n        rt_assert(0 <= rn && rn < runs);\n        const int32_t gp = para->run[rn].gp;\n        if (e->selection.a[1].gp != gp) {\n            // first Home keystroke moves caret to start of run\n            e->selection.a[1].gp = gp;\n        } else {\n            // second Home keystroke moves caret start of paragraph\n            e->selection.a[1].gp = 0;\n            if (e->scroll.pn >= e->selection.a[1].pn) { // scroll in\n                e->scroll.pn = e->selection.a[1].pn;\n                e->scroll.rn = 0;\n                ui_edit_invalidate_view(e);\n            }\n        }\n    }\n    if (!ui_app.shift) {\n        e->selection.a[0] = e->selection.a[1];\n    }\n    ui_edit_move_caret(e, e->selection.a[1]);\n}\n\nstatic void ui_edit_view_key_eol(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    int32_t pn = e->selection.a[1].pn;\n    int32_t gp = e->selection.a[1].gp;\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pn, &runs);\n    int32_t rn = ui_edit_pg_to_pr(e, e->selection.a[1]).rn;\n    rt_assert(0 <= rn && rn < runs);\n    if (rn == runs - 1) {\n        e->selection.a[1].gp = str->g;\n    } else if (e->selection.a[1].gp == str->g) {\n        // at the end of paragraph do nothing (or move caret to EOF?)\n    } else if (str->g > 0 && gp != run[rn].glyphs - 1) {\n        e->selection.a[1].gp = run[rn].gp + run[rn].glyphs - 1;\n    } else {\n        e->selection.a[1].gp = str->g;\n    }\n}\n\nstatic void ui_edit_view_key_end(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    if (ui_app.ctrl) {\n        int32_t py = e->inside.bottom;\n        for (int32_t i = dt->np - 1; i >= 0 && py >= ui_edit_line_height(e); i--) {\n            int32_t runs = ui_edit_paragraph_run_count(e, i);\n            for (int32_t j = runs - 1; j >= 0 && py >= ui_edit_line_height(e); j--) {\n                py -= ui_edit_line_height(e);\n                if (py < ui_edit_line_height(e)) {\n                    e->scroll.pn = i;\n                    e->scroll.rn = j;\n                }\n            }\n        }\n        e->selection.a[1] = ui_edit_text.end(dt);\n        ui_edit_invalidate_view(e);\n    } else {\n        ui_edit_view_key_eol(e);\n    }\n    if (!ui_app.shift) {\n        e->selection.a[0] = e->selection.a[1];\n    }\n    ui_edit_move_caret(e, e->selection.a[1]);\n}\n\nstatic void ui_edit_view_key_page_up(ui_edit_view_t* e) {\n    int32_t n = rt_max(1, e->visible_runs - 1);\n    ui_edit_pg_t scr = ui_edit_scroll_pg(e);\n    const ui_edit_pg_t prev = (ui_edit_pg_t){\n        .pn = rt_max(scr.pn - e->visible_runs - 1, 0),\n        .gp = 0\n    };\n    const int32_t m = ui_edit_runs_between(e, prev, scr);\n    if (m > n) {\n        ui_point_t pt = ui_edit_pg_to_xy(e, e->selection.a[1]);\n        ui_edit_pr_t scroll = e->scroll;\n        ui_edit_scroll_down(e, n);\n        if (scroll.pn != e->scroll.pn || scroll.rn != e->scroll.rn) {\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, pt.x, pt.y);\n            ui_edit_move_caret(e, pg);\n        }\n    } else {\n        const ui_edit_pg_t bof = {.pn = 0, .gp = 0};\n        ui_edit_move_caret(e, bof);\n    }\n}\n\nstatic void ui_edit_view_key_page_down(ui_edit_view_t* e) {\n    const ui_edit_text_t* dt = &e->doc->text; // document text\n    const int32_t n = rt_max(1, e->visible_runs - 1);\n    const ui_edit_pg_t scr = ui_edit_scroll_pg(e);\n    const ui_edit_pg_t next = (ui_edit_pg_t){\n        .pn = rt_min(scr.pn + 1, dt->np - 1),\n        .gp = scr.pn + 1 == dt->np - 1 ? dt->ps[dt->np - 1].g : 0\n    };\n    const int32_t m = ui_edit_runs_between(e, scr, next);\n    if (m > n) {\n        const ui_point_t pt = ui_edit_pg_to_xy(e, e->selection.a[1]);\n        const ui_edit_pr_t scroll = e->scroll;\n        ui_edit_scroll_up(e, n);\n        if (scroll.pn != e->scroll.pn || scroll.rn != e->scroll.rn) {\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, pt.x, pt.y);\n            ui_edit_move_caret(e, pg);\n        }\n    } else {\n        const ui_edit_pg_t end = ui_edit_text.end(dt);\n        ui_edit_move_caret(e, end);\n    }\n}\n\nstatic void ui_edit_view_key_delete(ui_edit_view_t* e) {\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    uint64_t f = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t t = ui_edit_range.uint64(e->selection.a[1]);\n    uint64_t end = ui_edit_range.uint64(ui_edit_text.end(dt));\n    if (f == t && t != end) {\n        ui_edit_pg_t s1 = e->selection.a[1];\n        ui_edit_view.key_right(e);\n        e->selection.a[1] = s1;\n    }\n    ui_edit_view.erase(e);\n}\n\nstatic void ui_edit_view_key_backspace(ui_edit_view_t* e) {\n    uint64_t f = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t t = ui_edit_range.uint64(e->selection.a[1]);\n    if (t != 0 && f == t) {\n        ui_edit_pg_t s1 = e->selection.a[1];\n        ui_edit_view.key_left(e);\n        e->selection.a[1] = s1;\n    }\n    ui_edit_view.erase(e);\n}\n\nstatic void ui_edit_view_key_enter(ui_edit_view_t* e) {\n    rt_assert(!e->ro);\n    if (!e->sle) {\n        ui_edit_view.erase(e);\n        e->selection.a[1] = ui_edit_insert_paragraph_break(e, e->selection.a[1]);\n        e->selection.a[0] = e->selection.a[1];\n        ui_edit_move_caret(e, e->selection.a[1]);\n    } else { // single line edit callback\n        if (ui_edit_view.enter != null) { ui_edit_view.enter(e); }\n    }\n}\n\nstatic bool ui_edit_view_key_pressed(ui_view_t* v, int64_t key) {\n    bool swallow = false;\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    if (e->focused) {\n        swallow = true;\n        if (key == ui.key.down && e->selection.a[1].pn < dt->np) {\n            ui_edit_view.key_down(e);\n        } else if (key == ui.key.up && dt->np > 1) {\n            ui_edit_view.key_up(e);\n        } else if (key == ui.key.left) {\n            ui_edit_view.key_left(e);\n        } else if (key == ui.key.right) {\n            ui_edit_view.key_right(e);\n        } else if (key == ui.key.page_up) {\n            ui_edit_view.key_page_up(e);\n        } else if (key == ui.key.page_down) {\n            ui_edit_view.key_page_down(e);\n        } else if (key == ui.key.home) {\n            ui_edit_view.key_home(e);\n        } else if (key == ui.key.end) {\n            ui_edit_view.key_end(e);\n        } else if (key == ui.key.del && !e->ro) {\n            ui_edit_view.key_delete(e);\n        } else if (key == ui.key.back && !e->ro) {\n            ui_edit_view.key_backspace(e);\n        } else if (key == ui.key.enter && !e->ro) {\n            ui_edit_view.key_enter(e);\n        } else {\n            swallow = false; // ignore other keys\n        }\n    }\n    return swallow;\n}\n\nstatic void ui_edit_undo(ui_edit_view_t* e) {\n    if (e->doc->undo != null) {\n        ui_edit_doc.undo(e->doc);\n    } else {\n        ui_app.beep(ui.beep.error);\n    }\n}\nstatic void ui_edit_redo(ui_edit_view_t* e) {\n    if (e->doc->redo != null) {\n        ui_edit_doc.redo(e->doc);\n    } else {\n        ui_app.beep(ui.beep.error);\n    }\n}\n\nstatic void ui_edit_character(ui_view_t* v, const char* utf8) {\n    rt_assert(v->type == ui_view_text);\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    #pragma push_macro(\"ui_edit_ctrl\")\n    #define ui_edit_ctrl(c) ((char)((c) - 'a' + 1))\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (e->focused) {\n        char ch = utf8[0];\n        if (ui_app.ctrl) {\n            if (ch == ui_edit_ctrl('a')) { ui_edit_view.select_all(e); }\n            if (ch == ui_edit_ctrl('c')) { ui_edit_view.copy(e); }\n            if (!e->ro) {\n                if (ch == ui_edit_ctrl('x')) { ui_edit_view.cut(e); }\n                if (ch == ui_edit_ctrl('v')) { ui_edit_view.paste(e); }\n                if (ch == ui_edit_ctrl('y')) { ui_edit_redo(e); }\n                if (ch == ui_edit_ctrl('z') || ch == ui_edit_ctrl('Z')) {\n                    if (ui_app.shift) { // Ctrl+Shift+Z\n                        ui_edit_redo(e);\n                    } else { // Ctrl+Z\n                        ui_edit_undo(e);\n                    }\n                }\n            }\n        }\n        if (0x20u <= (uint8_t)ch && !e->ro) { // 0x20 space\n            int32_t len = (int32_t)strlen(utf8);\n            int32_t bytes = rt_str.utf8bytes(utf8, len);\n            if (bytes > 0) {\n                ui_edit_view.erase(e); // remove selected text to be replaced by glyph\n                e->selection.a[1] = ui_edit_insert_inline(e,\n                    e->selection.a[1], utf8, bytes);\n                e->selection.a[0] = e->selection.a[1];\n                ui_edit_move_caret(e, e->selection.a[1]);\n            } else {\n                rt_println(\"invalid UTF8: 0x%02X%02X%02X%02X\",\n                        utf8[0], utf8[1], utf8[2], utf8[3]);\n            }\n        }\n    }\n    #pragma pop_macro(\"ui_edit_ctrl\")\n}\n\nstatic void ui_edit_select_word(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= pg.pn && 0 <= pg.gp) {\n        ui_edit_range_t r = ui_edit_word_range(e, pg);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, r.to.pn);\n        if (r.to.pn == r.from.pn && r.to.gp == r.from.gp && r.to.gp < glyphs) {\n            r.to.gp++; // at least one glyph to the right\n        }\n        if (ui_edit_range.compare(r.from, pg) != 0 ||\n            ui_edit_range.compare(r.to, pg) != 0) {\n            e->selection = r;\n            ui_edit_caret_to(e, r.to);\n//          rt_println(\"e->selection.a[1] = %d.%d\", to.pn, to.gp);\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n            e->edit.buttons = 0;\n        }\n    }\n}\n\nstatic void ui_edit_select_paragraph(ui_edit_view_t* e, int32_t x, int32_t y) {\n    ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t p = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= p.pn && 0 <= p.gp) {\n        ui_edit_range_t r = ui_edit_text.ordered(dt, &e->selection);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, p.pn);\n        if (p.gp > glyphs) { p.gp = rt_max(0, glyphs); }\n        if (p.pn == r.a[0].pn && r.a[0].pn == r.a[1].pn &&\n            r.a[0].gp <= p.gp && p.gp <= r.a[1].gp) {\n            r.a[0].gp = 0;\n            if (p.pn < dt->np - 1) {\n                r.a[1].pn = p.pn + 1;\n                r.a[1].gp = 0;\n            } else {\n                r.a[1].gp = dt->ps[p.pn].g;\n            }\n            e->selection = r;\n            ui_edit_caret_to(e, r.to);\n        }\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        e->edit.buttons = 0;\n    }\n}\n\nstatic void ui_edit_click(ui_edit_view_t* e, int32_t x, int32_t y) {\n    // x, y in 0..e->w, 0->e.h coordinate space\n    rt_assert(0 <= x && x < e->w && 0 <= y && y < e->h);\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n    if (0 <= pg.pn && 0 <= pg.gp && ui_view.has_focus(&e->view)) {\n        rt_swear(dt->np > 0 && pg.pn < dt->np);\n        int32_t glyphs = ui_edit_glyphs_in_paragraph(e, pg.pn);\n        if (pg.gp > glyphs) { pg.gp = rt_max(0, glyphs); }\n        ui_edit_move_caret(e, pg);\n    }\n}\n\nstatic void ui_edit_mouse_button_down(ui_edit_view_t* e, int32_t ix) {\n    e->edit.buttons |= (1 << ix);\n}\n\nstatic void ui_edit_mouse_button_up(ui_edit_view_t* e, int32_t ix) {\n    e->edit.buttons &= ~(1 << ix);\n}\n\nstatic bool ui_edit_tap(ui_view_t* v, int32_t rt_unused(ix), bool pressed) {\n    // `ix` ignored for now till context menu (copy/paste/select...)\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    // not just inside view but inside insets:\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside) {\n        if (pressed) {\n            e->edit.buttons = 0;\n            ui_edit_click(e, x, y);\n            ui_edit_mouse_button_down(e, ix);\n        } else if (!pressed) {\n            ui_edit_mouse_button_up(e, ix);\n        }\n    }\n    if (!pressed) { ui_edit_mouse_button_up(e, ix); }\n    return true;\n}\n\nstatic bool ui_edit_long_press(ui_view_t* v, int32_t rt_unused(ix)) {\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside && ui_edit_range.is_empty(e->selection)) {\n        ui_edit_select_paragraph(e, x, y);\n    }\n    return true;\n}\n\nstatic bool ui_edit_double_tap(ui_view_t* v, int32_t rt_unused(ix)) {\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    const int32_t x = ui_app.mouse.x - (v->x + e->inside.left);\n    const int32_t y = ui_app.mouse.y - (v->y + e->inside.top);\n    bool inside = 0 <= x && x < e->w && 0 <= y && y < e->h;\n    if (inside && e->selection.a[0].pn == e->selection.a[1].pn) {\n        ui_edit_select_word(e, x, y);\n    }\n    return false;\n}\n\nstatic void ui_edit_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    if (v->w > 0 && v->h > 0) {\n        const int32_t dy = dx_dy.y;\n        // TODO: maybe make a use of dx in single line no-word-break edit control?\n        if (ui_app.focus == v) {\n            rt_assert(v->type == ui_view_text);\n            ui_edit_view_t* e = (ui_edit_view_t*)v;\n            int32_t lines = (abs(dy) + ui_edit_line_height(e) - 1) / ui_edit_line_height(e);\n            if (dy > 0) {\n                ui_edit_scroll_down(e, lines);\n            } else if (dy < 0) {\n                ui_edit_scroll_up(e, lines);\n            }\n//  TODO: Ctrl UP/DW and caret of out of visible area scrolls are not\n//        implemented. Not sure they are very good UX experience.\n//        MacOS users may be used to scroll with touchpad, take a visual\n//        peek, do NOT click and continue editing at last cursor position.\n//        To me back forward stack navigation is much more intuitive and\n//        much mode \"modeless\" in spirit of cut/copy/paste. But opinions\n//        and editing habits vary. Easy to implement.\n            const int32_t x = e->caret.x - e->inside.left;\n            const int32_t y = e->caret.y - e->inside.top;\n            ui_edit_pg_t pg = ui_edit_xy_to_pg(e, x, y);\n            if (pg.pn >= 0 && pg.gp >= 0) {\n                rt_assert(pg.gp <= e->doc->text.ps[pg.pn].g);\n                ui_edit_move_caret(e, pg);\n            } else {\n                ui_edit_click(e, x, y);\n            }\n        }\n    }\n}\n\nstatic bool ui_edit_focus_gained(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    rt_assert(v->focusable);\n    if (ui_app.focused() && !e->focused) {\n        ui_edit_create_caret(e);\n        ui_edit_show_caret(e);\n        ui_edit_if_sle_layout(e);\n    }\n    e->edit.buttons = 0;\n    ui_app.request_redraw();\n    return true;\n}\n\nstatic void ui_edit_focus_lost(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (e->focused) {\n        ui_edit_hide_caret(e);\n        ui_edit_destroy_caret(e);\n        ui_edit_if_sle_layout(e);\n    }\n    e->edit.buttons = 0;\n    ui_app.request_redraw();\n}\n\nstatic void ui_edit_view_erase(ui_edit_view_t* e) {\n    if (e->selection.from.pn != e->selection.to.pn) {\n        ui_edit_invalidate_view(e);\n    } else {\n        ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n    }\n    ui_edit_range_t r = ui_edit_range.order(e->selection);\n    if (!ui_edit_range.is_empty(r) && ui_edit_doc.replace(e->doc, &r, null, 0)) {\n        e->selection = r;\n        e->selection.to = e->selection.from;\n        ui_edit_move_caret(e, e->selection.from);\n    }\n}\n\nstatic void ui_edit_select_all(ui_edit_view_t* e) {\n    e->selection = ui_edit_text.all_on_null(&e->doc->text, null);\n    ui_edit_invalidate_view(e);\n}\n\nstatic int32_t ui_edit_view_save(ui_edit_view_t* e, char* text, int32_t* bytes) {\n    rt_not_null(bytes);\n    enum {\n        error_insufficient_buffer = 122, // ERROR_INSUFFICIENT_BUFFER\n        error_more_data = 234            // ERROR_MORE_DATA\n    };\n    int32_t r = 0;\n    const int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, null);\n    if (text == null) {\n        *bytes = utf8bytes;\n        r = rt_core.error.more_data;\n    } else if (*bytes < utf8bytes) {\n        r = rt_core.error.insufficient_buffer;\n    } else {\n        ui_edit_doc.copy(e->doc, null, text, utf8bytes);\n        rt_assert(text[utf8bytes - 1] == 0x00);\n    }\n    return r;\n}\n\nstatic void ui_edit_view_copy(ui_edit_view_t* e) {\n    int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, &e->selection);\n    if (utf8bytes > 0) {\n        char* text = null;\n        bool ok = rt_heap.alloc((void**)&text, utf8bytes) == 0;\n        rt_swear(ok);\n        ui_edit_doc.copy(e->doc, &e->selection, text, utf8bytes);\n        rt_assert(text[utf8bytes - 1] == 0x00); // verify zero termination\n        rt_clipboard.put_text(text);\n        rt_heap.free(text);\n        static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n        int32_t x = e->x + e->caret.x;\n        int32_t y = e->y + e->caret.y - ui_edit_line_height(e);\n        if (y < ui_app.content->y) {\n            y += ui_edit_line_height(e) * 2;\n        }\n        if (y > ui_app.content->y + ui_app.content->h - ui_edit_line_height(e)) {\n            y = e->caret.y;\n        }\n        ui_app.show_hint(&hint, x, y, 0.5);\n    }\n}\n\nstatic void ui_edit_view_cut(ui_edit_view_t* e) {\n    int32_t utf8bytes = ui_edit_doc.utf8bytes(e->doc, &e->selection);\n    if (utf8bytes > 0) { ui_edit_view_copy(e); }\n    if (!e->ro) { ui_edit_view.erase(e); }\n}\n\nstatic ui_edit_pg_t ui_edit_paste_text(ui_edit_view_t* e,\n        const char* text, int32_t bytes) {\n    rt_assert(!e->ro);\n    ui_edit_text_t t = {0};\n    ui_edit_text.init(&t, text, bytes, false);\n    ui_edit_range_t r = ui_edit_text.all_on_null(&t, null);\n    ui_edit_doc.replace(e->doc, &e->selection, text, bytes);\n    ui_edit_pg_t pg = e->selection.from;\n    pg.pn += r.to.pn;\n    if (e->selection.from.pn == e->selection.to.pn && r.to.pn == 0) {\n        pg.gp = e->selection.from.gp + r.to.gp;\n    } else {\n        pg.gp = r.to.gp;\n    }\n    ui_edit_text.dispose(&t);\n    return pg;\n}\n\nstatic void ui_edit_view_replace(ui_edit_view_t* e, const char* s, int32_t n) {\n    if (!e->ro) {\n        if (n < 0) { n = (int32_t)strlen(s); }\n        ui_edit_view.erase(e);\n        e->selection.a[1] = ui_edit_paste_text(e, s, n);\n        e->selection.a[0] = e->selection.a[1];\n        if (e->w > 0) { ui_edit_move_caret(e, e->selection.a[1]); }\n    }\n}\n\nstatic void ui_edit_view_paste(ui_edit_view_t* e) {\n    if (!e->ro) {\n        ui_edit_pg_t pg = e->selection.a[1];\n        int32_t bytes = 0;\n        rt_clipboard.get_text(null, &bytes);\n        if (bytes > 0) {\n            char* text = null;\n            bool ok = rt_heap.alloc((void**)&text, bytes) == 0;\n            rt_swear(ok);\n            int32_t r = rt_clipboard.get_text(text, &bytes);\n            rt_fatal_if_error(r);\n            if (bytes > 0 && text[bytes - 1] == 0) {\n                bytes--; // clipboard includes zero terminator\n            }\n            if (bytes > 0) {\n                ui_edit_view.erase(e);\n                pg = ui_edit_paste_text(e, text, bytes);\n                ui_edit_move_caret(e, pg);\n            }\n            rt_heap.free(text);\n        }\n    }\n}\n\nstatic void ui_edit_prepare_sle(ui_edit_view_t* e) {\n    ui_view_t* v = &e->view;\n    rt_swear(e->sle && v->w > 0);\n    // shingle line edit is capable of resizing itself to two\n    // lines of text (and shrinking back) to avoid horizontal scroll\n    int32_t runs = rt_max(1, rt_min(2, ui_edit_paragraph_run_count(e, 0)));\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    int32_t h = insets.top + ui_edit_line_height(e) * runs + insets.bottom;\n    fp32_t min_h_em = (fp32_t)h / v->fm->em.h;\n    if (v->min_h_em != min_h_em) {\n        v->min_h_em = min_h_em;\n    }\n}\n\nstatic void ui_edit_insets(ui_edit_view_t* e) {\n    ui_view_t* v = &e->view;\n    const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n    e->inside = (ui_ltrb_t){\n        .left   = insets.left,\n        .top    = insets.top,\n        .right  = v->w - insets.right,\n        .bottom = v->h - insets.bottom\n    };\n    const int32_t width = e->edit.w; // previous width\n    e->edit.w = e->inside.right  - e->inside.left;\n    e->edit.h = e->inside.bottom - e->inside.top;\n    if (e->edit.w != width) { ui_edit_invalidate_all_runs(e); }\n}\n\nstatic void ui_edit_measure(ui_view_t* v) { // bottom up\n    rt_assert(v->type == ui_view_text);\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    if (v->w > 0 && e->sle) { ui_edit_prepare_sle(e); }\n    v->w = (int32_t)((fp64_t)v->fm->em.w * (fp64_t)v->min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)v->fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // enforce minimum size - it makes it checking corner cases much simpler\n    // and it's hard to edit anything in a smaller area - will result in bad UX\n    if (v->w < v->fm->em.w * 4) { v->w = i.left + v->fm->em.w * 4 + i.right; }\n    if (v->h < ui_edit_line_height(e))   { v->h = i.top + ui_edit_line_height(e) + i.bottom; }\n}\n\nstatic void ui_edit_layout(ui_view_t* v) { // top down\n    rt_assert(v->type == ui_view_text);\n    rt_assert(v->w > 0 && v->h > 0); // could be `if'\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_insets(e);\n    // fully visible runs\n    e->visible_runs = e->h / ui_edit_line_height(e);\n    ui_edit_invalidate_run(e, e->scroll.pn);\n    // number of runs in e->scroll.pn may have changed with e->w change\n    int32_t runs = ui_edit_paragraph_run_count(e, e->scroll.pn);\n    // glyph position in scroll_pn paragraph:\n    const ui_edit_pg_t scroll = v->w == 0 ? (ui_edit_pg_t){0, 0} :\n                                            ui_edit_scroll_pg(e);\n    e->scroll.rn = ui_edit_pg_to_pr(e, scroll).rn;\n    rt_assert(0 <= e->scroll.rn && e->scroll.rn < runs); (void)runs;\n    if (e->sle) { // single line edit (if changed on the fly):\n        e->selection.a[0].pn = 0; // only has single paragraph\n        e->selection.a[1].pn = 0;\n        // scroll line on top of current cursor position into view\n        const ui_edit_run_t* run = ui_edit_paragraph_runs(e, 0, &runs);\n        if (runs <= 2 && e->scroll.rn == 1) {\n            ui_edit_pg_t top = scroll;\n            top.gp = rt_max(0, top.gp - run[e->scroll.rn].glyphs - 1);\n            ui_edit_scroll_into_view(e, top);\n        }\n    }\n    ui_edit_scroll_into_view(e, e->selection.a[1]);\n    ui_edit_caret_to(e, e->selection.a[1]);\n    if (e->focused) {\n        // recreate caret because fm->height may have changed\n        ui_edit_hide_caret(e);\n        ui_edit_destroy_caret(e);\n        ui_edit_create_caret(e);\n        ui_edit_show_caret(e);\n        rt_assert(e->focused);\n    }\n}\n\nstatic void ui_edit_paint_selection(ui_edit_view_t* e, int32_t y, const ui_edit_run_t* r,\n        const char* text, int32_t pn, int32_t c0, int32_t c1) {\n    uint64_t s0 = ui_edit_range.uint64(e->selection.a[0]);\n    uint64_t e0 = ui_edit_range.uint64(e->selection.a[1]);\n    if (s0 > e0) {\n        uint64_t swap = e0;\n        e0 = s0;\n        s0 = swap;\n    }\n    const ui_edit_pg_t pnc0 = {.pn = pn, .gp = c0};\n    const ui_edit_pg_t pnc1 = {.pn = pn, .gp = c1};\n    uint64_t s1 = ui_edit_range.uint64(pnc0);\n    uint64_t e1 = ui_edit_range.uint64(pnc1);\n    if (s0 <= e1 && s1 <= e0) {\n        uint64_t start = rt_max(s0, s1) - (uint64_t)c0;\n        uint64_t end = rt_min(e0, e1) - (uint64_t)c0;\n        if (start < end) {\n            int32_t fro = (int32_t)start;\n            int32_t to  = (int32_t)end;\n            int32_t ofs0 = ui_edit_str.gp_to_bp(text, r->bytes, fro);\n            int32_t ofs1 = ui_edit_str.gp_to_bp(text, r->bytes, to);\n            rt_swear(ofs0 >= 0 && ofs1 >= 0);\n            int32_t x0 = ui_edit_text_width(e, text, ofs0);\n            int32_t x1 = ui_edit_text_width(e, text, ofs1);\n            // selection color is MSVC dark mode selection color\n            // TODO: need light mode selection color tpp\n            ui_color_t sc = ui_color_rgb(0x26, 0x4F, 0x78); // selection color\n            if (!e->focused || !ui_app.focused()) {\n                sc = ui_colors.darken(sc, 0.1f);\n            }\n            const ui_ltrb_t insets = ui_view.margins(&e->view, &e->insets);\n            int32_t x = e->x + insets.left;\n            // event if background is transparent\n            ui_gdi.fill(x + x0, y, x1 - x0, ui_edit_line_height(e), sc);\n        }\n    }\n}\n\nstatic int32_t ui_edit_paint_paragraph(ui_edit_view_t* e,\n        const ui_gdi_ta_t* ta, int32_t x, int32_t y, int32_t pn,\n        ui_rect_t rc) {\n    static const char* ww = rt_glyph_south_west_arrow_with_hook;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    rt_assert(0 <= pn && pn < dt->np);\n    const ui_edit_str_t* str = &dt->ps[pn];\n    int32_t runs = 0;\n    const ui_edit_run_t* run = ui_edit_paragraph_runs(e, pn, &runs);\n    for (int32_t j = ui_edit_first_visible_run(e, pn);\n                 j < runs && y < e->y + e->inside.bottom; j++) {\n//      rt_println(\"[%d.%d] @%d,%d bytes: %d\", pn, j, x, y, run[j].bytes);\n        if (rc.y - ui_edit_line_height(e) <= y && y < rc.y + rc.h) {\n            const char* text = str->u + run[j].bp;\n            ui_edit_paint_selection(e, y, &run[j], text, pn,\n                                    run[j].gp, run[j].gp + run[j].glyphs);\n            ui_gdi.text(ta, x, y, \"%.*s\", run[j].bytes, text);\n            if (j < runs - 1 && !e->hide_word_wrap) {\n                ui_gdi.text(ta, x + e->edit.w, y, \"%s\", ww);\n            }\n        }\n        y += ui_edit_line_height(e);\n    }\n    return y;\n}\n\nstatic void ui_edit_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_text);\n    rt_assert(!ui_view.is_hidden(v));\n    ui_edit_view_t* e = (ui_edit_view_t*)v;\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    // drawing text is really expensive, only paint what's needed:\n    ui_rect_t vrc = (ui_rect_t){v->x, v->y, v->w, v->h};\n    ui_rect_t rc;\n    if (ui.intersect_rect(&rc, &vrc, &ui_app.prc)) {\n        // because last line of the view may extend over the bottom\n        ui_gdi.set_clip(v->x, v->y, v->w, v->h);\n        ui_color_t b = v->background;\n        if (!ui_color_is_undefined(b) && !ui_color_is_transparent(b)) {\n            ui_gdi.fill(rc.x, rc.y, rc.w, rc.h, b);\n        }\n        const ui_ltrb_t insets = ui_view.margins(v, &v->insets);\n        int32_t x = v->x + insets.left;\n        int32_t y = v->y + insets.top;\n        const ui_gdi_ta_t ta = { .fm = v->fm, .color = v->color };\n        const int32_t pn = e->scroll.pn;\n        const int32_t bottom = v->y + e->inside.bottom;\n        rt_assert(pn < dt->np);\n        for (int32_t i = pn; i < dt->np && y < bottom; i++) {\n            y = ui_edit_paint_paragraph(e, &ta, x, y, i, rc);\n        }\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n}\n\nstatic void ui_edit_view_move(ui_edit_view_t* e, ui_edit_pg_t pg) {\n    if (e->w > 0) {\n        ui_edit_move_caret(e, pg); // may select text on move\n    } else {\n        e->selection.a[1] = pg;\n    }\n    e->selection.a[0] = e->selection.a[1];\n}\n\nstatic bool ui_edit_reallocate_runs(ui_edit_view_t* e, int32_t p, int32_t np) {\n    // This function is called in after() callback when\n    // d->text.np already changed to `new_np`.\n    // It has to manipulate e->para[] array w/o calling\n    // ui_edit_invalidate_runs() ui_edit_dispose_all_runs()\n    // because they assume that e->para[] array is in sync\n    // d->text.np.\n    ui_edit_text_t* dt = &e->doc->text; // document text\n    bool ok = true;\n    int32_t old_np = np;     // old (before) number of paragraphs\n    int32_t new_np = dt->np; // new (after)  number of paragraphs\n    rt_assert(old_np > 0 && new_np > 0 && e->para != null);\n    rt_assert(0 <= p && p < old_np);\n    if (old_np == new_np) {\n        ui_edit_invalidate_run(e, p);\n    } else if (new_np < old_np) { // shrinking - delete runs\n        const int32_t d = old_np - new_np; // `d` delta > 0\n        if (p + d < old_np - 1) {\n            const int32_t n = rt_max(0, old_np - p - d - 1);\n            memcpy(e->para + p + 1, e->para + p + 1 + d, n * sizeof(e->para[0]));\n        }\n        if (p < new_np) { ui_edit_invalidate_run(e, p); }\n        ok = rt_heap.realloc((void**)&e->para, new_np * sizeof(e->para[0])) == 0;\n        rt_swear(ok, \"shrinking\");\n    } else { // growing - insert runs\n        ui_edit_invalidate_run(e, p);\n        int32_t d = new_np - old_np;  // `d` delta > 0\n        ok = rt_heap.realloc_zero((void**)&e->para, new_np * sizeof(e->para[0])) == 0;\n        if (ok) {\n            const int32_t n = rt_max(0, new_np - p - d - 1);\n            memmove(e->para + p + 1 + d, e->para + p + 1,\n                    (size_t)n * sizeof(e->para[0]));\n            const int32_t m = rt_min(new_np, p + 1 + d);\n            for (int32_t i = p + 1; i < m; i++) {\n                e->para[i].run = null;\n                e->para[i].runs = 0;\n            }\n        }\n    }\n    return ok;\n}\n\nstatic void ui_edit_before(ui_edit_notify_t* notify,\n         const ui_edit_notify_info_t* ni) {\n    ui_edit_notify_view_t* n = (ui_edit_notify_view_t*)notify;\n    ui_edit_view_t* e = (ui_edit_view_t*)n->that;\n    rt_swear(e->doc == ni->d);\n    if (e->w > 0 && e->h > 0) {\n        const ui_edit_text_t* dt = &e->doc->text; // document text\n        rt_assert(dt->np > 0);\n        // `n->data` is number of paragraphs before replace():\n        n->data = (uintptr_t)dt->np;\n        if (e->selection.from.pn != e->selection.to.pn) {\n            ui_edit_invalidate_view(e);\n        } else {\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        }\n    }\n}\n\nstatic void ui_edit_after(ui_edit_notify_t* notify,\n         const ui_edit_notify_info_t* ni) {\n    ui_edit_notify_view_t* n = (ui_edit_notify_view_t*)notify;\n    ui_edit_view_t* e = (ui_edit_view_t*)n->that;\n    const ui_edit_text_t* dt = &ni->d->text; // document text\n    rt_assert(ni->d == e->doc && dt->np > 0);\n    if (e->w > 0 && e->h > 0) {\n        // number of paragraphs before replace():\n        const int32_t np = (int32_t)n->data;\n        rt_swear(dt->np == np - ni->deleted + ni->inserted);\n        ui_edit_reallocate_runs(e, ni->r->from.pn, np);\n        e->selection = *ni->x;\n        // this is needed by undo/redo: trim selection\n        ui_edit_pg_t* pg = e->selection.a;\n        for (int32_t i = 0; i < rt_countof(e->selection.a); i++) {\n            pg[i].pn = rt_max(0, rt_min(dt->np - 1, pg[i].pn));\n            pg[i].gp = rt_max(0, rt_min(dt->ps[pg[i].pn].g, pg[i].gp));\n        }\n        if (ni->r->from.pn != ni->r->to.pn &&\n            ni->x->from.pn != ni->x->to.pn &&\n            ni->r->from.pn == ni->x->from.pn) {\n            ui_edit_invalidate_rect(e, ui_edit_selection_rect(e));\n        } else {\n            ui_edit_invalidate_view(e);\n        }\n        ui_edit_scroll_into_view(e, e->selection.to);\n    }\n}\n\nstatic void ui_edit_view_init(ui_edit_view_t* e, ui_edit_doc_t* d) {\n    memset(e, 0, sizeof(*e));\n    rt_assert(d != null && d->text.np > 0);\n    e->doc = d;\n    rt_assert(d->text.np > 0);\n    e->listener.that = (void*)e;\n    e->listener.data = 0;\n    e->listener.notify.before = ui_edit_before;\n    e->listener.notify.after  = ui_edit_after;\n    rt_static_assertion(offsetof(ui_edit_notify_view_t, notify) == 0);\n    ui_edit_doc.subscribe(d, &e->listener.notify);\n    e->color_id = ui_color_id_window_text;\n    e->background_id = ui_color_id_window;\n    e->fm = &ui_app.fm.prop.normal;\n    e->insets  = (ui_margins_t){ 0.25, 0.25, 0.50, 0.25 };\n    e->padding = (ui_margins_t){ 0.25, 0.25, 0.25, 0.25 };\n    e->min_w_em = 1.0;\n    e->min_h_em = 1.0;\n    e->type = ui_view_text;\n    e->focusable = true;\n    e->last_x    = -1;\n    e->focused   = false;\n    e->sle       = false;\n    e->ro        = false;\n    e->caret        = (ui_point_t){-1, -1};\n    e->paint        = ui_edit_paint;\n    e->measure      = ui_edit_measure;\n    e->layout       = ui_edit_layout;\n    e->tap          = ui_edit_tap;\n    e->long_press   = ui_edit_long_press;\n    e->double_tap   = ui_edit_double_tap;\n    e->character    = ui_edit_character;\n    e->focus_gained = ui_edit_focus_gained;\n    e->focus_lost   = ui_edit_focus_lost;\n    e->key_pressed  = ui_edit_view_key_pressed;\n    e->mouse_scroll = ui_edit_mouse_scroll;\n    ui_edit_allocate_runs(e);\n    if (e->debug.id == null) { e->debug.id = \"#edit\"; }\n}\n\nstatic void ui_edit_view_dispose(ui_edit_view_t* e) {\n    ui_edit_doc.unsubscribe(e->doc, &e->listener.notify);\n    ui_edit_dispose_all_runs(e);\n    memset(e, 0, sizeof(*e));\n}\n\nui_edit_view_if ui_edit_view = {\n    .init                 = ui_edit_view_init,\n    .set_font             = ui_edit_view_set_font,\n    .move                 = ui_edit_view_move,\n    .replace              = ui_edit_view_replace,\n    .save                 = ui_edit_view_save,\n    .erase                = ui_edit_view_erase,\n    .cut                  = ui_edit_view_cut,\n    .copy                 = ui_edit_view_copy,\n    .paste                = ui_edit_view_paste,\n    .select_all           = ui_edit_select_all,\n    .key_down             = ui_edit_view_key_down,\n    .key_up               = ui_edit_view_key_up,\n    .key_left             = ui_edit_view_key_left,\n    .key_right            = ui_edit_view_key_right,\n    .key_page_up          = ui_edit_view_key_page_up,\n    .key_page_down        = ui_edit_view_key_page_down,\n    .key_home             = ui_edit_view_key_home,\n    .key_end              = ui_edit_view_key_end,\n    .key_delete           = ui_edit_view_key_delete,\n    .key_backspace        = ui_edit_view_key_backspace,\n    .key_enter            = ui_edit_view_key_enter,\n    .fuzz                 = null,\n    .dispose              = ui_edit_view_dispose\n};\n"
  },
  {
    "path": "src/ui/ui_fuzzing.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\n// TODO: Ctrl+A Ctrl+V Ctrl+C Ctrl+X Ctrl+Z Ctrl+Y\n\nstatic bool     ui_fuzzing_debug = true;\nstatic uint32_t ui_fuzzing_seed;\nstatic bool     ui_fuzzing_running;\nstatic bool     ui_fuzzing_inside;\n\nstatic ui_fuzzing_t ui_fuzzing_work;\n\nstatic const char* lorem_ipsum_words[] = {\n    \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\",\n    \"elit\", \"quisque\", \"faucibus\", \"ex\", \"sapien\", \"vitae\", \"pellentesque\",\n    \"sem\", \"placerat\", \"in\", \"id\", \"cursus\", \"mi\", \"pretium\", \"tellus\",\n    \"duis\", \"convallis\", \"tempus\", \"leo\", \"eu\", \"aenean\", \"sed\", \"diam\",\n    \"urna\", \"tempor\", \"pulvinar\", \"vivamus\", \"fringilla\", \"lacus\", \"nec\",\n    \"metus\", \"bibendum\", \"egestas\", \"iaculis\", \"massa\", \"nisl\",\n    \"malesuada\", \"lacinia\", \"integer\", \"nunc\", \"posuere\", \"ut\", \"hendrerit\",\n    \"semper\", \"vel\", \"class\", \"aptent\", \"taciti\", \"sociosqu\", \"ad\", \"litora\",\n    \"torquent\", \"per\", \"conubia\", \"nostra\", \"inceptos\",\n    \"himenaeos\", \"orci\", \"varius\", \"natoque\", \"penatibus\", \"et\", \"magnis\",\n    \"dis\", \"parturient\", \"montes\", \"nascetur\", \"ridiculus\", \"mus\", \"donec\",\n    \"rhoncus\", \"eros\", \"lobortis\", \"nulla\", \"molestie\", \"mattis\",\n    \"scelerisque\", \"maximus\", \"eget\", \"fermentum\", \"odio\", \"phasellus\",\n    \"non\", \"purus\", \"est\", \"efficitur\", \"laoreet\", \"mauris\", \"pharetra\",\n    \"vestibulum\", \"fusce\", \"dictum\", \"risus\", \"blandit\", \"quis\",\n    \"suspendisse\", \"aliquet\", \"nisi\", \"sodales\", \"consequat\", \"magna\",\n    \"ante\", \"condimentum\", \"neque\", \"at\", \"luctus\", \"nibh\", \"finibus\",\n    \"facilisis\", \"dapibus\", \"etiam\", \"interdum\", \"tortor\", \"ligula\",\n    \"congue\", \"sollicitudin\", \"erat\", \"viverra\", \"ac\", \"tincidunt\", \"nam\",\n    \"porta\", \"elementum\", \"a\", \"enim\", \"euismod\", \"quam\", \"justo\",\n    \"lectus\", \"commodo\", \"augue\", \"arcu\", \"dignissim\", \"velit\", \"aliquam\",\n    \"imperdiet\", \"mollis\", \"nullam\", \"volutpat\", \"porttitor\",\n    \"ullamcorper\", \"rutrum\", \"gravida\", \"cras\", \"eleifend\", \"turpis\",\n    \"fames\", \"primis\", \"vulputate\", \"ornare\", \"sagittis\", \"vehicula\",\n    \"praesent\", \"dui\", \"felis\", \"venenatis\", \"ultrices\", \"proin\", \"libero\",\n    \"feugiat\", \"tristique\", \"accumsan\", \"maecenas\", \"potenti\", \"ultricies\",\n    \"habitant\", \"morbi\", \"senectus\", \"netus\", \"suscipit\", \"auctor\",\n    \"curabitur\", \"facilisi\", \"cubilia\", \"curae\", \"hac\", \"habitasse\",\n    \"platea\", \"dictumst\"\n};\n\n#define ui_fuzzing_lorem_ipsum_canonique \\\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do \"         \\\n    \"eiusmod  tempor incididunt ut labore et dolore magna aliqua.Ut enim ad \"  \\\n    \"minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \" \\\n    \"ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \"      \\\n    \"voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur \"  \\\n    \"sint occaecat cupidatat non proident, sunt in culpa qui officia \"         \\\n    \"deserunt mollit anim id est laborum.\"\n\n#define ui_fuzzing_lorem_ipsum_chinese \\\n    \"\\xE6\\x88\\x91\\xE6\\x98\\xAF\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE6\\x96\\x87\\xE6\\x9C\\xAC\\xE7\\x9A\\x84\\xE4\" \\\n    \"\\xBD\\x8D\\xE7\\xBD\\xAE\\xE3\\x80\\x82\\xE8\\xBF\\x99\\xE9\\x87\\x8C\\xE6\\x94\\xBE\\xE7\\xBD\\xAE\\xE4\\xBA\" \\\n    \"\\x86\\xE5\\x81\\x87\\xE6\\x96\\x87\\xE5\\x81\\x87\\xE5\\xAD\\x97\\xE3\\x80\\x82\\xE5\\xB8\\x8C\\xE6\\x9C\\x9B\" \\\n    \"\\xE8\\xBF\\x99\\xE4\\xBA\\x9B\\xE6\\x96\\x87\\xE5\\xAD\\x97\\xE5\\x8F\\xAF\\xE4\\xBB\\xA5\\xE5\\xA1\\xAB\\xE5\" \\\n    \"\\x85\\x85\\xE7\\xA9\\xBA\\xE7\\x99\\xBD\\xE3\\x80\\x82\";\n\n#define ui_fuzzing_lorem_ipsum_japanese \\\n    \"\\xE3\\x81\\x93\\xE3\\x82\\x8C\\xE3\\x81\\xAF\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\" \\\n    \"\\x82\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x81\\xA7\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE3\\x81\\x93\\xE3\\x81\" \\\n    \"\\x93\\xE3\\x81\\xAB\\xE6\\x96\\x87\\xE7\\xAB\\xA0\\xE3\\x81\\x8C\\xE5\\x85\\xA5\\xE3\\x82\\x8A\\xE3\\x81\\xBE\" \\\n    \"\\xE3\\x81\\x99\\xE3\\x80\\x82\\xE8\\xAA\\xAD\\xE3\\x81\\xBF\\xE3\\x82\\x84\\xE3\\x81\\x99\\xE3\\x81\\x84\\xE3\" \\\n    \"\\x82\\x88\\xE3\\x81\\x86\\xE3\\x81\\xAB\\xE3\\x83\\x80\\xE3\\x83\\x9F\\xE3\\x83\\xBC\\xE3\\x83\\x86\\xE3\\x82\" \\\n    \"\\xAD\\xE3\\x82\\xB9\\xE3\\x83\\x88\\xE3\\x82\\x92\\xE4\\xBD\\xBF\\xE7\\x94\\xA8\\xE3\\x81\\x97\\xE3\\x81\\xA6\" \\\n    \"\\xE3\\x81\\x84\\xE3\\x81\\xBE\\xE3\\x81\\x99\\xE3\\x80\\x82\";\n\n\n#define ui_fuzzing_lorem_ipsum_korean \\\n    \"\\xEC\\x9D\\xB4\\xEA\\xB2\\x83\\xEC\\x9D\\x80\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\\x85\\x8D\\xEC\\x8A\" \\\n    \"\\xA4\\xED\\x8A\\xB8\\xEC\\x9E\\x85\\xEB\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\\x20\\xEC\\x97\\xAC\\xEA\\xB8\\xB0\\xEC\" \\\n    \"\\x97\\x90\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEB\\x93\\x9C\\xEC\\x96\\xB4\\xEA\\xB0\\x80\" \\\n    \"\\xEB\\x8A\\x94\\x20\\xEB\\xAC\\xB8\\xEC\\x9E\\x90\\xEA\\xB0\\x80\\x20\\xEC\\x9E\\x88\\xEB\\x8B\\xA4\\x2E\\x20\" \\\n    \"\\xEC\\x9D\\xBD\\xEA\\xB8\\xB0\\x20\\xEC\\x89\\xBD\\xEA\\xB2\\x8C\\x20\\xEB\\x8D\\x94\\xEB\\xAF\\xB8\\x20\\xED\" \\\n    \"\\x85\\x8D\\xEC\\x8A\\xA4\\xED\\x8A\\xB8\\xEB\\xA5\\xBC\\x20\\xEC\\x82\\xAC\\xEC\\x9A\\xA9\\xED\\x95\\xA9\\xEB\" \\\n    \"\\x8B\\x88\\xEB\\x8B\\xA4\\x2E\";\n\n#define ui_fuzzing_lorem_ipsum_emoji \\\n    \"\\xF0\\x9F\\x8D\\x95\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA6\\x84\\xF0\\x9F\\x92\\xBB\\xF0\\x9F\\x8E\\x89\\xF0\\x9F\" \\\n    \"\\x8C\\x88\\xF0\\x9F\\x90\\xB1\\xF0\\x9F\\x93\\x9A\\xF0\\x9F\\x8E\\xA8\\xF0\\x9F\\x8D\\x94\\xF0\\x9F\\x8D\\xA6\" \\\n    \"\\xF0\\x9F\\x8E\\xB8\\xF0\\x9F\\xA7\\xA9\\xF0\\x9F\\x8D\\xBF\\xF0\\x9F\\x93\\xB7\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\" \\\n    \"\\x91\\xBE\\xF0\\x9F\\x8C\\xAE\\xF0\\x9F\\x8E\\x88\\xF0\\x9F\\x9A\\xB2\\xF0\\x9F\\x8D\\xA9\\xF0\\x9F\\x8E\\xAE\" \\\n    \"\\xF0\\x9F\\x8D\\x89\\xF0\\x9F\\x8E\\xAC\\xF0\\x9F\\x90\\xB6\\xF0\\x9F\\x93\\xB1\\xF0\\x9F\\x8E\\xB9\\xF0\\x9F\" \\\n    \"\\xA6\\x96\\xF0\\x9F\\x8C\\x9F\\xF0\\x9F\\x8D\\xAD\\xF0\\x9F\\x8E\\xA4\\xF0\\x9F\\x8F\\x96\\xF0\\x9F\\xA6\\x8B\" \\\n    \"\\xF0\\x9F\\x8E\\xB2\\xF0\\x9F\\x8E\\xAF\\xF0\\x9F\\x8D\\xA3\\xF0\\x9F\\x9A\\x81\\xF0\\x9F\\x8E\\xAD\\xF0\\x9F\" \\\n    \"\\x91\\x9F\\xF0\\x9F\\x9A\\x82\\xF0\\x9F\\x8D\\xAA\\xF0\\x9F\\x8E\\xBB\\xF0\\x9F\\x9B\\xB8\\xF0\\x9F\\x8C\\xBD\" \\\n    \"\\xF0\\x9F\\x93\\x80\\xF0\\x9F\\x9A\\x80\\xF0\\x9F\\xA7\\x81\\xF0\\x9F\\x93\\xAF\\xF0\\x9F\\x8C\\xAF\\xF0\\x9F\" \\\n    \"\\x90\\xA5\\xF0\\x9F\\xA7\\x83\\xF0\\x9F\\x8D\\xBB\\xF0\\x9F\\x8E\\xAE\";\n\ntypedef struct {\n    char* text;\n    int32_t count; // at least 1KB\n    uint32_t seed; // seed for random generator\n    int32_t min_paragraphs; // at least 1\n    int32_t max_paragraphs;\n    int32_t min_sentences; // at least 1\n    int32_t max_sentences;\n    int32_t min_words; // at least 2\n    int32_t max_words;\n    const char* append; // append after each paragraph (e.g. extra \"\\n\")\n} ui_fuzzing_generator_params_t;\n\nstatic uint32_t ui_fuzzing_random(void) {\n    return rt_num.random32(&ui_fuzzing_seed);\n}\n\nstatic fp64_t ui_fuzzing_random_fp64(void) {\n    uint32_t r = ui_fuzzing_random();\n    return (fp64_t)r / (fp64_t)UINT32_MAX;\n}\n\nstatic void ui_fuzzing_generator(ui_fuzzing_generator_params_t p) {\n    rt_fatal_if(p.count < 1024); // at least 1KB expected\n    rt_fatal_if_not(0 < p.min_paragraphs && p.min_paragraphs <= p.max_paragraphs);\n    rt_fatal_if_not(0 < p.min_sentences && p.min_sentences <= p.max_sentences);\n    rt_fatal_if_not(2 < p.min_words && p.min_words <= p.max_words);\n    char* s = p.text;\n    // assume longest word is less than 128\n    char* end = p.text + p.count - 128;\n    uint32_t paragraphs = p.min_paragraphs +\n        (p.min_paragraphs == p.max_paragraphs ? 0 :\n         rt_num.random32(&p.seed) % (p.max_paragraphs - p.min_paragraphs + 1));\n    while (paragraphs > 0 && s < end) {\n        uint32_t sentences_in_paragraph = p.min_sentences +\n            (p.min_sentences == p.max_sentences ? 0 :\n             rt_num.random32(&p.seed) % (p.max_sentences - p.min_sentences + 1));\n        while (sentences_in_paragraph > 0 && s < end) {\n            const uint32_t words_in_sentence = p.min_words +\n                (p.min_words == p.max_words ? 0 :\n                 rt_num.random32(&p.seed) % (p.max_words - p.min_words + 1));\n            for (uint32_t i = 0; i < words_in_sentence && s < end; i++) {\n                const int32_t ix = rt_num.random32(&p.seed) %\n                                   rt_countof(lorem_ipsum_words);\n                const char* word = lorem_ipsum_words[ix];\n                memcpy(s, word, strlen(word));\n                if (i == 0) { *s = (char)toupper(*s); }\n                s += strlen(word);\n                if (i < words_in_sentence - 1 && s < end) {\n                    const char* delimiter = \"\\x20\";\n                    int32_t punctuation = rt_num.random32(&p.seed) % 128;\n                    switch (punctuation) {\n                        case 0:\n                        case 1:\n                        case 2: delimiter = \", \"; break;\n                        case 3:\n                        case 4: delimiter = \"; \"; break;\n                        case 6: delimiter = \": \"; break;\n                        case 7: delimiter = \" - \"; break;\n                        default: break;\n                    }\n                    memcpy(s, delimiter, strlen(delimiter));\n                    s += strlen(delimiter);\n                }\n            }\n            if (sentences_in_paragraph > 1 && s < end) {\n                memcpy(s, \".\\x20\", 2);\n                s += 2;\n            } else {\n                *s++ = '.';\n            }\n            sentences_in_paragraph--;\n        }\n        if (paragraphs > 1 && s < end) {\n            *s++ = '\\n';\n        }\n        if (p.append != null && p.append[0] != 0) {\n            memcpy(s, p.append, strlen(p.append));\n            s += strlen(p.append);\n        }\n        paragraphs--;\n    }\n    *s = 0;\n//  rt_println(\"%s\\n\", p.text);\n}\n\nstatic void ui_fuzzing_next_gibberish(int32_t number_of_characters,\n        char text[]) {\n    static fp64_t freq[96] = {\n        0.1716, 0.0023, 0.0027, 0.0002, 0.0001, 0.0005, 0.0013, 0.0012,\n        0.0015, 0.0014, 0.0017, 0.0002, 0.0084, 0.0020, 0.0075, 0.0040,\n        0.0135, 0.0045, 0.0053, 0.0053, 0.0047, 0.0047, 0.0043, 0.0047,\n        0.0057, 0.0044, 0.0037, 0.0004, 0.0016, 0.0004, 0.0017, 0.0017,\n        0.0020, 0.0045, 0.0026, 0.0020, 0.0027, 0.0021, 0.0025, 0.0026,\n        0.0030, 0.0025, 0.0021, 0.0018, 0.0028, 0.0026, 0.0024, 0.0020,\n        0.0025, 0.0026, 0.0030, 0.0022, 0.0027, 0.0022, 0.0020, 0.0023,\n        0.0015, 0.0016, 0.0009, 0.0005, 0.0005, 0.0001, 0.0003, 0.0003,\n        0.0078, 0.0013, 0.0012, 0.0008, 0.0012, 0.0007, 0.0006, 0.0011,\n        0.0016, 0.0012, 0.0011, 0.0004, 0.0004, 0.0016, 0.0013, 0.0009,\n        0.0009, 0.0008, 0.0013, 0.0011, 0.0013, 0.0012, 0.0006, 0.0007,\n        0.0011, 0.0005, 0.0007, 0.0003, 0.0002, 0.0006, 0.0002, 0.0005\n    };\n    static fp64_t cumulative_freq[96];\n    static bool initialized = 0;\n    if (!initialized) {\n        cumulative_freq[0] = freq[0];\n        for (int i = 1; i < rt_countof(freq); i++) {\n            cumulative_freq[i] = cumulative_freq[i - 1] + freq[i];\n        }\n        initialized = 1;\n    }\n    int32_t i = 0;\n    while (i < number_of_characters) {\n        text[i] = 0x00;\n        fp64_t r = ui_fuzzing_random_fp64();\n        for (int j = 0; j < 96 && text[i] == 0; j++) {\n            if (r < cumulative_freq[j]) {\n                text[i] = (char)(0x20 + j);\n            }\n        }\n        if (text[i] != 0) { i++; }\n    }\n    text[number_of_characters] = 0x00;\n}\n\nstatic void ui_fuzzing_dispatch(ui_fuzzing_t* work) {\n    rt_swear(work == &ui_fuzzing_work);\n    ui_app.alt = work->alt;\n    ui_app.ctrl = work->ctrl;\n    ui_app.shift = work->shift;\n    if (work->utf8 != null && work->utf8[0] != 0) {\n        ui_view.character(ui_app.content, work->utf8);\n        work->utf8 = work->utf8[1] == 0 ? null : work->utf8++;\n    } else if (work->key != 0) {\n        ui_view.key_pressed(ui_app.content, work->key);\n        ui_view.key_released(ui_app.content, work->key);\n        work->key = 0;\n    } else if (work->pt != null) {\n        const int32_t x = work->pt->x;\n        const int32_t y = work->pt->y;\n        ui_app.mouse.x = x;\n        ui_app.mouse.y = y;\n//      https://stackoverflow.com/questions/22259936/\n//      https://stackoverflow.com/questions/65691101/\n//      rt_println(\"%d,%d\", x + ui_app.wrc.x, y + ui_app.wrc.y);\n//      // next line works only when running as administrator:\n//      rt_fatal_win32err(SetCursorPos(x + ui_app.wrc.x, y + ui_app.wrc.y));\n        const bool l_button = ui_app.mouse_left  != work->left;\n        const bool r_button = ui_app.mouse_right != work->right;\n        ui_app.mouse_left  = work->left;\n        ui_app.mouse_right = work->right;\n        ui_view.mouse_move(ui_app.content);\n        if (l_button) {\n            ui_view.tap(ui_app.content, 0, work->left);\n        }\n        if (r_button) {\n            ui_view.tap(ui_app.content, 2, work->right);\n        }\n        work->pt = null;\n    } else {\n        rt_assert(false, \"TODO: ?\");\n    }\n    if (ui_fuzzing_running) {\n        if (ui_fuzzing.next == null) {\n            ui_fuzzing.next_random(work);\n        } else {\n            ui_fuzzing.next(work);\n        }\n    }\n}\n\nstatic void ui_fuzzing_do_work(rt_work_t* p) {\n    if (ui_fuzzing_running) {\n        ui_fuzzing_inside = true;\n        if (ui_fuzzing.custom != null) {\n            ui_fuzzing.custom((ui_fuzzing_t*)p);\n        } else {\n            ui_fuzzing.dispatch((ui_fuzzing_t*)p);\n        }\n        ui_fuzzing_inside = false;\n    } else {\n        // fuzzing has been .stop()-ed drop it\n    }\n}\n\nstatic void ui_fuzzing_post(void) {\n    ui_app.post(&ui_fuzzing_work.base);\n}\n\nstatic void ui_fuzzing_alt_ctrl_shift(void) {\n    ui_fuzzing_t* w = &ui_fuzzing_work;\n    switch (ui_fuzzing_random() % 8) {\n        case 0: w->alt = 0; w->ctrl = 0; w->shift = 0; break;\n        case 1: w->alt = 1; w->ctrl = 0; w->shift = 0; break;\n        case 2: w->alt = 0; w->ctrl = 1; w->shift = 0; break;\n        case 3: w->alt = 1; w->ctrl = 1; w->shift = 0; break;\n        case 4: w->alt = 0; w->ctrl = 0; w->shift = 1; break;\n        case 5: w->alt = 1; w->ctrl = 0; w->shift = 1; break;\n        case 6: w->alt = 0; w->ctrl = 1; w->shift = 1; break;\n        case 7: w->alt = 1; w->ctrl = 1; w->shift = 1; break;\n        default: rt_assert(false);\n    }\n}\n\nstatic void ui_fuzzing_character(void) {\n    static char utf8[4 * 1024];\n    if (ui_fuzzing_work.utf8 == null) {\n        fp64_t r = ui_fuzzing_random_fp64();\n        if (r < 0.125) {\n            uint32_t rnd = ui_fuzzing_random();\n            int32_t n = (int32_t)rt_max(1, rnd % 32);\n            ui_fuzzing_next_gibberish(n, utf8);\n            ui_fuzzing_work.utf8 = utf8;\n            if (ui_fuzzing_debug) {\n    //          rt_println(\"%s\", utf8);\n            }\n        } else if (r < 0.25) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_chinese;\n        } else if (r < 0.375) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_japanese;\n        } else if (r < 0.5) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_korean;\n        } else if (r < 0.5 + 0.125) {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_emoji;\n        } else {\n            ui_fuzzing_work.utf8 = ui_fuzzing_lorem_ipsum_canonique;\n        }\n    }\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_key(void) {\n    struct {\n        int32_t key;\n        const char* name;\n    } keys[] = {\n        { ui.key.up,        \"up\",     },\n        { ui.key.down,      \"down\",   },\n        { ui.key.left,      \"left\",   },\n        { ui.key.right,     \"right\",  },\n        { ui.key.home,      \"home\",   },\n        { ui.key.end,       \"end\",    },\n        { ui.key.page_up,   \"pgup\",   },\n        { ui.key.page_down, \"pgdw\",   },\n        { ui.key.insert,    \"insert\"  },\n        { ui.key.enter,     \"enter\"   },\n        { ui.key.del,       \"delete\"  },\n        { ui.key.back,      \"back\"    },\n    };\n    ui_fuzzing_alt_ctrl_shift();\n    uint32_t ix = ui_fuzzing_random() % rt_countof(keys);\n    if (ui_fuzzing_debug) {\n//      rt_println(\"key(%s)\", keys[ix].name);\n    }\n    ui_fuzzing_work.key = keys[ix].key;\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_mouse(void) {\n    // mouse events only inside edit control otherwise\n    // they will start clicking buttons around\n    ui_view_t* v = ui_app.content;\n    ui_fuzzing_t* w = &ui_fuzzing_work;\n    int32_t x = ui_fuzzing_random() % v->w;\n    int32_t y = ui_fuzzing_random() % v->h;\n    static ui_point_t pt;\n    pt = (ui_point_t){ x + v->x, y + v->y };\n    if (ui_fuzzing_random() % 2) {\n        w->left  = !w->left;\n    }\n    if (ui_fuzzing_random() % 2) {\n        w->right = !w->right;\n    }\n    if (ui_fuzzing_debug) {\n//      rt_println(\"mouse(%d,%d) %s%s\", pt.x, pt.y,\n//              w->left ? \"L\" : \"_\", w->right ? \"R\" : \"_\");\n    }\n    w->pt = &pt;\n    ui_fuzzing_post();\n}\n\nstatic void ui_fuzzing_start(uint32_t seed) {\n    ui_fuzzing_seed = seed | 0x1;\n    ui_fuzzing_running = true;\n    if (ui_fuzzing.next == null) {\n        ui_fuzzing.next_random(&ui_fuzzing_work);\n    } else {\n        ui_fuzzing.next(&ui_fuzzing_work);\n    }\n}\n\nstatic bool ui_fuzzing_is_running(void) {\n    return ui_fuzzing_running;\n}\n\nstatic bool ui_fuzzing_from_inside(void) {\n    return ui_fuzzing_inside;\n}\n\nstatic void ui_fuzzing_stop(void) {\n    ui_fuzzing_running = false;\n}\n\nstatic void ui_fuzzing_next_random(ui_fuzzing_t* f) {\n    rt_swear(f == &ui_fuzzing_work);\n    ui_fuzzing_work = (ui_fuzzing_t){\n        .base = { .when = rt_clock.seconds() + 0.001, // 1ms\n                  .work = ui_fuzzing_do_work },\n    };\n    uint32_t rnd = ui_fuzzing_random() % 100;\n    if (rnd < 80) {\n        ui_fuzzing_character();\n    } else if (rnd < 90) {\n        ui_fuzzing_key();\n    } else {\n        ui_fuzzing_mouse();\n    }\n}\n\nui_fuzzing_if ui_fuzzing = {\n    .start       = ui_fuzzing_start,\n    .is_running  = ui_fuzzing_is_running,\n    .from_inside = ui_fuzzing_from_inside,\n    .next_random = ui_fuzzing_next_random,\n    .dispatch    = ui_fuzzing_dispatch,\n    .next        = null,\n    .custom      = null,\n    .stop        = ui_fuzzing_stop\n};\n"
  },
  {
    "path": "src/ui/ui_gdi.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"rt/rt_win32.h\"\n\n#pragma push_macro(\"ui_gdi_with_hdc\")\n#pragma push_macro(\"ui_gdi_hdc_with_font\")\n\nstatic ui_brush_t  ui_gdi_brush_hollow;\nstatic ui_brush_t  ui_gdi_brush_color;\nstatic ui_pen_t    ui_gdi_pen_hollow;\nstatic ui_region_t ui_gdi_clip;\n\ntypedef struct ui_gdi_context_s {\n    HDC hdc; // window canvas() or memory DC\n    int32_t background_mode;\n    int32_t stretch_mode;\n    ui_pen_t pen;\n    ui_font_t font;\n    ui_color_t text_color;\n    POINT brush_origin;\n    ui_brush_t brush;\n    HBITMAP texture;\n} ui_gdi_context_t;\n\nstatic ui_gdi_context_t ui_gdi_context;\n\n#define ui_gdi_hdc() (ui_gdi_context.hdc)\n\nstatic void ui_gdi_init(void) {\n    ui_gdi_brush_hollow = (ui_brush_t)GetStockBrush(HOLLOW_BRUSH);\n    ui_gdi_brush_color  = (ui_brush_t)GetStockBrush(DC_BRUSH);\n    ui_gdi_pen_hollow = (ui_pen_t)GetStockPen(NULL_PEN);\n}\n\nstatic void ui_gdi_fini(void) {\n    if (ui_gdi_clip != null) {\n        rt_fatal_win32err(DeleteRgn(ui_gdi_clip));\n    }\n    ui_gdi_clip = null;\n}\n\nstatic ui_pen_t ui_gdi_set_pen(ui_pen_t p) {\n    rt_not_null(p);\n    return (ui_pen_t)SelectPen(ui_gdi_hdc(), (HPEN)p);\n}\n\nstatic ui_brush_t ui_gdi_set_brush(ui_brush_t b) {\n    rt_not_null(b);\n    return (ui_brush_t)SelectBrush(ui_gdi_hdc(), b);\n}\n\nstatic uint32_t ui_gdi_color_rgb(ui_color_t c) {\n    rt_assert(ui_color_is_8bit(c));\n    return (COLORREF)(c & 0xFFFFFFFF);\n}\n\nstatic COLORREF ui_gdi_color_ref(ui_color_t c) {\n    return ui_gdi.color_rgb(c);\n}\n\nstatic ui_color_t ui_gdi_set_text_color(ui_color_t c) {\n    return SetTextColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n}\n\nstatic ui_font_t ui_gdi_set_font(ui_font_t f) {\n    rt_not_null(f);\n    return (ui_font_t)SelectFont(ui_gdi_hdc(), (HFONT)f);\n}\n\nstatic void ui_gdi_begin(ui_bitmap_t* image) {\n    rt_swear(ui_gdi_context.hdc == null, \"no nested begin()/end()\");\n    if (image != null) {\n        rt_swear(image->texture != null);\n        ui_gdi_context.hdc = CreateCompatibleDC((HDC)ui_app.canvas);\n        ui_gdi_context.texture = SelectBitmap(ui_gdi_hdc(),\n                                             (HBITMAP)image->texture);\n    } else {\n        ui_gdi_context.hdc = (HDC)ui_app.canvas;\n        rt_swear(ui_gdi_context.texture == null);\n    }\n    ui_gdi_context.font  = ui_gdi_set_font(ui_app.fm.prop.normal.font);\n    ui_gdi_context.pen   = ui_gdi_set_pen(ui_gdi_pen_hollow);\n    ui_gdi_context.brush = ui_gdi_set_brush(ui_gdi_brush_hollow);\n    rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0,\n        &ui_gdi_context.brush_origin));\n    ui_color_t tc = ui_colors.get_color(ui_color_id_window_text);\n    ui_gdi_context.text_color = ui_gdi_set_text_color(tc);\n    ui_gdi_context.background_mode = SetBkMode(ui_gdi_hdc(), TRANSPARENT);\n    ui_gdi_context.stretch_mode = SetStretchBltMode(ui_gdi_hdc(), HALFTONE);\n}\n\nstatic void ui_gdi_end(void) {\n    rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(),\n                   ui_gdi_context.brush_origin.x,\n                   ui_gdi_context.brush_origin.y, null));\n    ui_gdi_set_brush(ui_gdi_context.brush);\n    ui_gdi_set_pen(ui_gdi_context.pen);\n    ui_gdi_set_text_color(ui_gdi_context.text_color);\n    SetBkMode(ui_gdi_hdc(), ui_gdi_context.background_mode);\n    SetStretchBltMode(ui_gdi_hdc(), ui_gdi_context.stretch_mode);\n    if (ui_gdi_context.hdc != (HDC)ui_app.canvas) {\n        rt_swear(ui_gdi_context.texture != null); // 1x1 bitmap\n        SelectBitmap(ui_gdi_context.hdc, (HBITMAP)ui_gdi_context.texture);\n        rt_fatal_win32err(DeleteDC(ui_gdi_context.hdc));\n    }\n    memset(&ui_gdi_context, 0x00, sizeof(ui_gdi_context));\n}\n\nstatic ui_pen_t ui_gdi_set_colored_pen(ui_color_t c) {\n    ui_pen_t p = (ui_pen_t)SelectPen(ui_gdi_hdc(), GetStockPen(DC_PEN));\n    SetDCPenColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n    return p;\n}\n\nstatic ui_pen_t ui_gdi_create_pen(ui_color_t c, int32_t width) {\n    rt_assert(width >= 1);\n    ui_pen_t pen = (ui_pen_t)CreatePen(PS_SOLID, width, ui_gdi_color_ref(c));\n    rt_not_null(pen);\n    return pen;\n}\n\nstatic void ui_gdi_delete_pen(ui_pen_t p) {\n    rt_fatal_win32err(DeletePen(p));\n}\n\nstatic ui_brush_t ui_gdi_create_brush(ui_color_t c) {\n    return (ui_brush_t)CreateSolidBrush(ui_gdi_color_ref(c));\n}\n\nstatic void ui_gdi_delete_brush(ui_brush_t b) {\n    DeleteBrush((HBRUSH)b);\n}\n\nstatic ui_color_t ui_gdi_set_brush_color(ui_color_t c) {\n    return SetDCBrushColor(ui_gdi_hdc(), ui_gdi_color_ref(c));\n}\n\nstatic void ui_gdi_set_clip(int32_t x, int32_t y, int32_t w, int32_t h) {\n    if (ui_gdi_clip != null) { DeleteRgn(ui_gdi_clip); ui_gdi_clip = null; }\n    if (w > 0 && h > 0) {\n        ui_gdi_clip = (ui_region_t)CreateRectRgn(x, y, x + w, y + h);\n        rt_not_null(ui_gdi_clip);\n    }\n    rt_fatal_if(SelectClipRgn(ui_gdi_hdc(), (HRGN)ui_gdi_clip) == ERROR);\n}\n\nstatic void ui_gdi_pixel(int32_t x, int32_t y, ui_color_t c) {\n    rt_not_null(ui_app.canvas);\n    rt_fatal_win32err(SetPixel(ui_gdi_hdc(), x, y, ui_gdi_color_ref(c)));\n}\n\nstatic void ui_gdi_rectangle(int32_t x, int32_t y, int32_t w, int32_t h) {\n    rt_fatal_win32err(Rectangle(ui_gdi_hdc(), x, y, x + w, y + h));\n}\n\nstatic void ui_gdi_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1,\n        ui_color_t c) {\n    POINT pt;\n    rt_fatal_win32err(MoveToEx(ui_gdi_hdc(), x0, y0, &pt));\n    ui_pen_t p = ui_gdi_set_colored_pen(c);\n    rt_fatal_win32err(LineTo(ui_gdi_hdc(), x1, y1));\n    ui_gdi_set_pen(p);\n    rt_fatal_win32err(MoveToEx(ui_gdi_hdc(), pt.x, pt.y, null));\n}\n\nstatic void ui_gdi_frame(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t c) {\n    ui_brush_t b = ui_gdi_set_brush(ui_gdi_brush_hollow);\n    ui_pen_t p = ui_gdi_set_colored_pen(c);\n    ui_gdi_rectangle(x, y, w, h);\n    ui_gdi_set_pen(p);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_rect(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t border, ui_color_t fill) {\n    const bool tf = ui_color_is_transparent(fill);   // transparent fill\n    const bool tb = ui_color_is_transparent(border); // transparent border\n    ui_brush_t b = tf ? ui_gdi_brush_hollow : ui_gdi_brush_color;\n    b = ui_gdi_set_brush(b);\n    ui_color_t c = tf ? ui_colors.transparent : ui_gdi_set_brush_color(fill);\n    ui_pen_t p = tb ? ui_gdi_set_pen(ui_gdi_pen_hollow) :\n                      ui_gdi_set_colored_pen(border);\n    ui_gdi_rectangle(x, y, w, h);\n    if (!tf) { ui_gdi_set_brush_color(c); }\n    ui_gdi_set_pen(p);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_fill(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t c) {\n//  rt_println(\"%d,%d %dx%d 0x%08X\", x, y, w, h, (uint32_t)c);\n    ui_brush_t b = ui_gdi_set_brush(ui_gdi_brush_color);\n    c = ui_gdi_set_brush_color(c);\n    RECT rc = { x, y, x + w, y + h };\n    HBRUSH brush = (HBRUSH)GetCurrentObject(ui_gdi_hdc(), OBJ_BRUSH);\n    rt_fatal_win32err(FillRect(ui_gdi_hdc(), &rc, brush));\n    ui_gdi_set_brush_color(c);\n    ui_gdi_set_brush(b);\n}\n\nstatic void ui_gdi_poly(ui_point_t* points, int32_t count, ui_color_t c) {\n    // make sure ui_point_t and POINT have the same memory layout:\n    static_assert(sizeof(points->x) == sizeof(((POINT*)0)->x), \"ui_point_t\");\n    static_assert(sizeof(points->y) == sizeof(((POINT*)0)->y), \"ui_point_t\");\n    static_assert(sizeof(points[0]) == sizeof(*((POINT*)0)), \"ui_point_t\");\n    rt_assert(ui_gdi_hdc() != null && count > 1);\n    ui_pen_t pen = ui_gdi_set_colored_pen(c);\n    rt_fatal_win32err(Polyline(ui_gdi_hdc(), (POINT*)points, count));\n    ui_gdi_set_pen(pen);\n}\n\nstatic void ui_gdi_circle(int32_t x, int32_t y, int32_t radius,\n        ui_color_t border, ui_color_t fill) {\n    rt_swear(!ui_color_is_transparent(border) || ui_color_is_transparent(fill));\n    // Win32 GDI even radius drawing looks ugly squarish and asymmetrical.\n    rt_swear(radius % 2 == 1, \"radius: %d must be odd\");\n    if (ui_color_is_transparent(border)) {\n        rt_assert(!ui_color_is_transparent(fill));\n        border = fill;\n    }\n    rt_assert(!ui_color_is_transparent(border));\n    const bool tf = ui_color_is_transparent(fill);   // transparent fill\n    ui_brush_t brush = tf ? ui_gdi_set_brush(ui_gdi_brush_hollow) :\n                        ui_gdi_set_brush(ui_gdi_brush_color);\n    ui_color_t c = tf ? ui_colors.transparent : ui_gdi_set_brush_color(fill);\n    ui_pen_t p = ui_gdi_set_colored_pen(border);\n    HDC hdc = ui_gdi_context.hdc;\n    int32_t l = x - radius;\n    int32_t t = y - radius;\n    int32_t r = x + radius + 1;\n    int32_t b = y + radius + 1;\n    Ellipse(hdc, l, t, r, b);\n//  SetPixel(hdc, x, y, RGB(255, 255, 255));\n    ui_gdi_set_pen(p);\n    if (!tf) { ui_gdi_set_brush_color(c); }\n    ui_gdi_set_brush(brush);\n}\n\nstatic void ui_gdi_fill_rounded(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t fill) {\n    int32_t r = x + w - 1; // right\n    int32_t b = y + h - 1; // bottom\n    ui_gdi_circle(x + radius, y + radius, radius, fill, fill);\n    ui_gdi_circle(r - radius, y + radius, radius, fill, fill);\n    ui_gdi_circle(x + radius, b - radius, radius, fill, fill);\n    ui_gdi_circle(r - radius, b - radius, radius, fill, fill);\n    // rectangles\n    ui_gdi.fill(x + radius, y, w - radius * 2, h, fill);\n    r = x + w - radius;\n    ui_gdi.fill(x, y + radius, radius, h - radius * 2, fill);\n    ui_gdi.fill(r, y + radius, radius, h - radius * 2, fill);\n}\n\nstatic void ui_gdi_rounded_border(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t border) {\n    {\n        int32_t r = x + w - 1; // right\n        int32_t b = y + h - 1; // bottom\n        ui_gdi.set_clip(x, y, radius + 1, radius + 1);\n        ui_gdi_circle(x + radius, y + radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(r - radius, y, radius + 1, radius + 1);\n        ui_gdi_circle(r - radius, y + radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(x, b - radius, radius + 1, radius + 1);\n        ui_gdi_circle(x + radius, b - radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(r - radius, b - radius, radius + 1, radius + 1);\n        ui_gdi_circle(r - radius, b - radius, radius, border, ui_colors.transparent);\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n    {\n        int32_t r = x + w - 1; // right\n        int32_t b = y + h - 1; // bottom\n        ui_gdi.line(x + radius, y, r - radius + 1, y, border);\n        ui_gdi.line(x + radius, b, r - radius + 1, b, border);\n        ui_gdi.line(x - 1, y + radius, x - 1, b - radius + 1, border);\n        ui_gdi.line(r + 1, y + radius, r + 1, b - radius + 1, border);\n    }\n}\n\nstatic void ui_gdi_rounded(int32_t x, int32_t y, int32_t w, int32_t h,\n        int32_t radius, ui_color_t border, ui_color_t fill) {\n    rt_swear(!ui_color_is_transparent(border) || !ui_color_is_transparent(fill));\n    if (!ui_color_is_transparent(fill)) {\n        ui_gdi_fill_rounded(x, y, w, h, radius, fill);\n    }\n    if (!ui_color_is_transparent(border)) {\n        ui_gdi_rounded_border(x, y, w, h, radius, border);\n    }\n}\n\nstatic void ui_gdi_gradient(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_color_t rgba_from, ui_color_t rgba_to, bool vertical) {\n    TRIVERTEX vertex[2] = {0};\n    vertex[0].x = x;\n    vertex[0].y = y;\n    // TODO: colors:\n    vertex[0].Red   = (COLOR16)(((rgba_from >>  0) & 0xFF) << 8);\n    vertex[0].Green = (COLOR16)(((rgba_from >>  8) & 0xFF) << 8);\n    vertex[0].Blue  = (COLOR16)(((rgba_from >> 16) & 0xFF) << 8);\n    vertex[0].Alpha = (COLOR16)(((rgba_from >> 24) & 0xFF) << 8);\n    vertex[1].x = x + w;\n    vertex[1].y = y + h;\n    vertex[1].Red   = (COLOR16)(((rgba_to >>  0) & 0xFF) << 8);\n    vertex[1].Green = (COLOR16)(((rgba_to >>  8) & 0xFF) << 8);\n    vertex[1].Blue  = (COLOR16)(((rgba_to >> 16) & 0xFF) << 8);\n    vertex[1].Alpha = (COLOR16)(((rgba_to >> 24) & 0xFF) << 8);\n    GRADIENT_RECT gRect = {0, 1};\n    const uint32_t mode = vertical ?\n        GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H;\n    GradientFill(ui_gdi_hdc(), vertex, 2, &gRect, 1, mode);\n}\n\nstatic BITMAPINFO* ui_gdi_greyscale_bitmap_info(void) {\n    typedef struct bitmap_rgb_s {\n        BITMAPINFO bi;\n        RGBQUAD rgb[256];\n    } bitmap_rgb_t;\n    static bitmap_rgb_t storage; // for gs palette\n    static BITMAPINFO* bi = &storage.bi;\n    BITMAPINFOHEADER* bih = &bi->bmiHeader;\n    if (bih->biSize == 0) { // once\n        bih->biSize = sizeof(BITMAPINFOHEADER);\n        for (int32_t i = 0; i < 256; i++) {\n            RGBQUAD* q = &bi->bmiColors[i];\n            q->rgbReserved = 0;\n            q->rgbBlue = q->rgbGreen = q->rgbRed = (uint8_t)i;\n        }\n        bih->biPlanes = 1;\n        bih->biBitCount = 8;\n        bih->biCompression = BI_RGB;\n        bih->biClrUsed = 256;\n        bih->biClrImportant = 256;\n    }\n    return bi;\n}\n\nstatic void ui_gdi_pixels(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        int32_t bpp, const uint8_t* pixels) {\n    if (bpp == 1) {\n        ui_gdi.greyscale(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else if (bpp == 3) {\n        ui_gdi.bgr(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else if (bpp == 4) {\n        ui_gdi.bgrx(dx, dy, dw, dh, ix, iy, iw, ih, width, height, stride, pixels);\n    } else {\n        rt_fatal(\"bpp: %d not {1, 3, 4}\", bpp);\n    }\n}\n\nstatic void ui_gdi_greyscale(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride, const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFO *bi = ui_gdi_greyscale_bitmap_info(); // global! not thread safe\n        BITMAPINFOHEADER* bih = &bi->bmiHeader;\n        bih->biWidth = width;\n        bih->biHeight = -height; // top down image\n        bih->biSizeImage = (DWORD)(iw * abs(ih));\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n                    pixels, bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic BITMAPINFOHEADER ui_gdi_bgrx_init_bi(int32_t w, int32_t h, int32_t bpp) {\n    rt_assert(w > 0 && h >= 0); // h cannot be negative?\n    BITMAPINFOHEADER bi = {\n        .biSize = sizeof(BITMAPINFOHEADER),\n        .biPlanes = 1,\n        .biBitCount = (uint16_t)(bpp * 8),\n        .biCompression = BI_RGB,\n        .biWidth = w,\n        .biHeight = -h, // top down image\n        .biSizeImage = (DWORD)(w * abs(h) * bpp),\n        .biClrUsed = 0,\n        .biClrImportant = 0\n   };\n   return bi;\n}\n\n// bgr(width) assumes strides are padded and rounded up to 4 bytes\n// if this is not the case use ui_gdi.bitmap_init() that will unpack\n// and align scanlines prior to draw\n\nstatic void ui_gdi_bgr(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width * 3 + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFOHEADER bi = ui_gdi_bgrx_init_bi(width, height, 3);\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n                    pixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic void ui_gdi_bgrx(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        int32_t width, int32_t height, int32_t stride,\n        const uint8_t* pixels) {\n    rt_fatal_if(stride != ((width * 4 + 3) & ~0x3));\n    rt_assert(iw > 0 && ih != 0); // h can be negative\n    if (iw > 0 && ih != 0) {\n        BITMAPINFOHEADER bi = ui_gdi_bgrx_init_bi(width, height, 4);\n        POINT pt = { 0 };\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), 0, 0, &pt));\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh,\n                                                ix, iy, iw, ih,\n            pixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS, SRCCOPY) == 0);\n        rt_fatal_win32err(SetBrushOrgEx(ui_gdi_hdc(), pt.x, pt.y, &pt));\n    }\n}\n\nstatic BITMAPINFO* ui_gdi_init_bitmap_info(int32_t w, int32_t h, int32_t bpp,\n        BITMAPINFO* bi) {\n    rt_assert(w > 0 && h >= 0); // h cannot be negative?\n    bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\n    bi->bmiHeader.biWidth = w;\n    bi->bmiHeader.biHeight = -h;  // top down image\n    bi->bmiHeader.biPlanes = 1;\n    bi->bmiHeader.biBitCount = (uint16_t)(bpp * 8);\n    bi->bmiHeader.biCompression = BI_RGB;\n    bi->bmiHeader.biSizeImage = (DWORD)(w * abs(h) * bpp);\n    return bi;\n}\n\nstatic void ui_gdi_create_dib_section(ui_bitmap_t* image, int32_t w, int32_t h,\n        int32_t bpp) {\n    rt_fatal_if(image->texture != null, \"bitmap_dispose() not called?\");\n    // not using GetWindowDC(ui_app.window) will allow to initialize images\n    // before window is created\n    HDC c = CreateCompatibleDC(null); // GetWindowDC(ui_app.window);\n    BITMAPINFO local = { {sizeof(BITMAPINFOHEADER)} };\n    BITMAPINFO* bi = bpp == 1 ? ui_gdi_greyscale_bitmap_info() : &local;\n    image->texture = (ui_texture_t)CreateDIBSection(c, \n            ui_gdi_init_bitmap_info(w, h, bpp, bi),\n            DIB_RGB_COLORS, &image->pixels, null, 0x0\n    );\n    rt_fatal_if(image->texture == null || image->pixels == null);\n    rt_fatal_win32err(DeleteDC(c));\n}\n\nstatic void ui_gdi_bitmap_init_rgbx(ui_bitmap_t* image, int32_t w, int32_t h,\n        int32_t bpp, const uint8_t* pixels) {\n    bool swapped = bpp < 0;\n    bpp = abs(bpp);\n    rt_fatal_if(bpp != 4, \"bpp: %d\", bpp);\n    ui_gdi_create_dib_section(image, w, h, bpp);\n    const int32_t stride = (w * bpp + 3) & ~0x3;\n    uint8_t* scanline = image->pixels;\n    const uint8_t* rgbx = pixels;\n    if (!swapped) {\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgra[0] = rgbx[2];\n                bgra[1] = rgbx[1];\n                bgra[2] = rgbx[0];\n                bgra[3] = 0xFF;\n                bgra += 4;\n                rgbx += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    } else {\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgra[0] = rgbx[0];\n                bgra[1] = rgbx[1];\n                bgra[2] = rgbx[2];\n                bgra[3] = 0xFF;\n                bgra += 4;\n                rgbx += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    }\n    image->w = w;\n    image->h = h;\n    image->bpp = bpp;\n    image->stride = stride;\n}\n\nstatic void ui_gdi_bitmap_init(ui_bitmap_t* image, int32_t w, int32_t h, int32_t bpp,\n        const uint8_t* pixels) {\n    bool swapped = bpp < 0;\n    bpp = abs(bpp);\n    rt_fatal_if(bpp < 0 || bpp == 2 || bpp > 4, \"bpp=%d not {1, 3, 4}\", bpp);\n    ui_gdi_create_dib_section(image, w, h, bpp);\n    // Win32 bitmaps stride is rounded up to 4 bytes\n    const int32_t stride = (w * bpp + 3) & ~0x3;\n    uint8_t* scanline = image->pixels;\n    if (bpp == 1) {\n        for (int32_t y = 0; y < h; y++) {\n            memcpy(scanline, pixels, (size_t)w);\n            pixels += w;\n            scanline += stride;\n        }\n    } else if (bpp == 3 && !swapped) {\n        const uint8_t* rgb = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgr = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgr[0] = rgb[2];\n                bgr[1] = rgb[1];\n                bgr[2] = rgb[0];\n                bgr += 3;\n                rgb += 3;\n            }\n            pixels += w * bpp;\n            scanline += stride;\n        }\n    } else if (bpp == 3 && swapped) {\n        const uint8_t* rgb = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgr = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                bgr[0] = rgb[0];\n                bgr[1] = rgb[1];\n                bgr[2] = rgb[2];\n                bgr += 3;\n                rgb += 3;\n            }\n            pixels += w * bpp;\n            scanline += stride;\n        }\n    } else if (bpp == 4 && !swapped) {\n        // premultiply alpha, see:\n        // https://stackoverflow.com/questions/24595717/alphablend-generating-incorrect-colors\n        const uint8_t* rgba = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                int32_t alpha = rgba[3];\n                bgra[0] = (uint8_t)(rgba[2] * alpha / 255);\n                bgra[1] = (uint8_t)(rgba[1] * alpha / 255);\n                bgra[2] = (uint8_t)(rgba[0] * alpha / 255);\n                bgra[3] = rgba[3];\n                bgra += 4;\n                rgba += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    } else if (bpp == 4 && swapped) {\n        // premultiply alpha, see:\n        // https://stackoverflow.com/questions/24595717/alphablend-generating-incorrect-colors\n        const uint8_t* rgba = pixels;\n        for (int32_t y = 0; y < h; y++) {\n            uint8_t* bgra = scanline;\n            for (int32_t x = 0; x < w; x++) {\n                int32_t alpha = rgba[3];\n                bgra[0] = (uint8_t)(rgba[0] * alpha / 255);\n                bgra[1] = (uint8_t)(rgba[1] * alpha / 255);\n                bgra[2] = (uint8_t)(rgba[2] * alpha / 255);\n                bgra[3] = rgba[3];\n                bgra += 4;\n                rgba += 4;\n            }\n            pixels += w * 4;\n            scanline += stride;\n        }\n    }\n    image->w = w;\n    image->h = h;\n    image->bpp = bpp;\n    image->stride = stride;\n}\n\nstatic void ui_gdi_alpha(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* image, fp64_t alpha) {\n    rt_assert(image->bpp > 0);\n    rt_assert(0 <= alpha && alpha <= 1);\n    rt_not_null(ui_gdi_hdc());\n    HDC c = CreateCompatibleDC(ui_gdi_hdc());\n    rt_not_null(c);\n    HBITMAP zero1x1 = SelectBitmap((HDC)c, (HBITMAP)image->texture);\n    BLENDFUNCTION bf = { 0 };\n    bf.SourceConstantAlpha = (uint8_t)(0xFF * alpha + 0.49);\n    if (image->bpp == 4) {\n        bf.BlendOp = AC_SRC_OVER;\n        bf.BlendFlags = 0;\n        bf.AlphaFormat = AC_SRC_ALPHA;\n    } else {\n        bf.BlendOp = AC_SRC_OVER;\n        bf.BlendFlags = 0;\n        bf.AlphaFormat = 0;\n    }\n    rt_assert(0 <= ix && ix < image->w && 0 <= iy && iy < image->h);\n    rt_assert(ix + iw <= image->w && iy + ih <= image->h);\n    rt_fatal_win32err(AlphaBlend(ui_gdi_hdc(), dx, dy, dw, dh,\n                                 c, ix, iy, iw, ih, bf));\n    SelectBitmap((HDC)c, zero1x1);\n    rt_fatal_win32err(DeleteDC(c));\n}\n\nstatic void ui_gdi_bitmap(int32_t dx, int32_t dy, int32_t dw, int32_t dh,\n        int32_t ix, int32_t iy, int32_t iw, int32_t ih,\n        ui_bitmap_t* image) {\n    rt_assert(image->bpp == 1 || image->bpp == 3 || image->bpp == 4);\n    rt_assert(0 <= ix && ix < image->w && 0 <= iy && iy < image->h);\n    rt_assert(ix + iw <= image->w && iy + ih <= image->h);\n    rt_not_null(ui_gdi_hdc());\n    if (image->bpp == 1) { // StretchBlt() is bad for greyscale\n        BITMAPINFO* bi   = ui_gdi_greyscale_bitmap_info();\n        BITMAPINFO* info = ui_gdi_init_bitmap_info(image->w, image->h, 1, bi);\n        rt_fatal_if(StretchDIBits(ui_gdi_hdc(), dx, dy, dw, dh, ix, iy, iw, ih,\n            image->pixels, info, DIB_RGB_COLORS, SRCCOPY) == 0);\n    } else {\n        HDC c = CreateCompatibleDC(ui_gdi_hdc());\n        rt_not_null(c);\n        HBITMAP zero1x1 = SelectBitmap(c, image->texture);\n        rt_fatal_win32err(StretchBlt(ui_gdi_hdc(), dx, dy, dw, dh,\n            c, ix, iy, iw, ih, SRCCOPY));\n        SelectBitmap(c, zero1x1);\n        rt_fatal_win32err(DeleteDC(c));\n    }\n}\n\nstatic void ui_gdi_icon(int32_t x, int32_t y, int32_t w, int32_t h,\n        ui_icon_t icon) {\n    DrawIconEx(ui_gdi_hdc(), x, y, (HICON)icon, w, h, 0, NULL, DI_NORMAL | DI_COMPAT);\n}\n\nstatic void ui_gdi_cleartype(bool on) {\n    enum { spif = SPIF_UPDATEINIFILE | SPIF_SENDCHANGE };\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHING,\n                                                   true, 0, spif));\n    uintptr_t s = on ? FE_FONTSMOOTHINGCLEARTYPE : FE_FONTSMOOTHINGSTANDARD;\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHINGTYPE,\n        0, (void*)s, spif));\n}\n\nstatic void ui_gdi_font_smoothing_contrast(int32_t c) {\n    rt_fatal_if(!(c == -1 || 1000 <= c && c <= 2200), \"contrast: %d\", c);\n    if (c == -1) { c = 1400; }\n    rt_fatal_win32err(SystemParametersInfoA(SPI_SETFONTSMOOTHINGCONTRAST,\n        0, (void*)(uintptr_t)c, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE));\n}\n\nrt_static_assertion(ui_gdi_font_quality_default == DEFAULT_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_draft == DRAFT_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_proof == PROOF_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_nonantialiased == NONANTIALIASED_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_antialiased == ANTIALIASED_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_cleartype == CLEARTYPE_QUALITY);\nrt_static_assertion(ui_gdi_font_quality_cleartype_natural == CLEARTYPE_NATURAL_QUALITY);\n\nstatic ui_font_t ui_gdi_create_font(const char* family, int32_t h, int32_t q) {\n    rt_assert(h > 0);\n    LOGFONTA lf = {0};\n    int32_t n = GetObjectA(ui_app.fm.prop.normal.font, sizeof(lf), &lf);\n    rt_fatal_if(n != (int32_t)sizeof(lf));\n    lf.lfHeight = -h;\n    rt_str_printf(lf.lfFaceName, \"%s\", family);\n    if (ui_gdi_font_quality_default <= q &&\n        q <= ui_gdi_font_quality_cleartype_natural) {\n        lf.lfQuality = (uint8_t)q;\n    } else {\n        rt_fatal_if(q != -1, \"use -1 for do not care quality\");\n    }\n    return (ui_font_t)CreateFontIndirectA(&lf);\n}\n\nstatic ui_font_t ui_gdi_font(ui_font_t f, int32_t h, int32_t q) {\n    rt_assert(f != null && h > 0);\n    LOGFONTA lf = {0};\n    int32_t n = GetObjectA(f, sizeof(lf), &lf);\n    rt_fatal_if(n != (int32_t)sizeof(lf));\n    lf.lfHeight = -h;\n    if (ui_gdi_font_quality_default <= q &&\n        q <= ui_gdi_font_quality_cleartype_natural) {\n        lf.lfQuality = (uint8_t)q;\n    } else {\n        rt_fatal_if(q != -1, \"use -1 for do not care quality\");\n    }\n    return (ui_font_t)CreateFontIndirectA(&lf);\n}\n\nstatic void ui_gdi_delete_font(ui_font_t f) {\n    rt_fatal_win32err(DeleteFont(f));\n}\n\n// guaranteed to return dc != null even if not painting\n\nstatic HDC ui_gdi_get_dc(void) {\n    rt_not_null(ui_app.window);\n    HDC hdc = ui_gdi_hdc() != null ?\n              ui_gdi_hdc() : GetDC((HWND)ui_app.window);\n    rt_not_null(hdc);\n    return hdc;\n}\n\nstatic void ui_gdi_release_dc(HDC hdc) {\n    if (ui_gdi_hdc() == null) {\n        ReleaseDC((HWND)ui_app.window, hdc);\n    }\n}\n\n#define ui_gdi_with_hdc(code) do {           \\\n    HDC hdc = ui_gdi_get_dc();               \\\n    code                                     \\\n    ui_gdi_release_dc(hdc);                  \\\n} while (0)\n\n#define ui_gdi_hdc_with_font(f, ...) do {    \\\n    rt_not_null(f);                          \\\n    HDC hdc = ui_gdi_get_dc();               \\\n    HFONT font_ = SelectFont(hdc, (HFONT)f); \\\n    { __VA_ARGS__ }                          \\\n    SelectFont(hdc, font_);                  \\\n    ui_gdi_release_dc(hdc);                  \\\n} while (0)\n\nstatic void ui_gdi_dump_hdc_fm(HDC hdc) {\n    // https://en.wikipedia.org/wiki/Quad_(typography)\n    // https://learn.microsoft.com/en-us/windows/win32/gdi/string-widths-and-heights\n    // https://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-font\n    // Amazingly same since Windows 3.1 1992\n    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-textmetrica\n    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-outlinetextmetrica\n    TEXTMETRICA tm = {0};\n    rt_fatal_win32err(GetTextMetricsA(hdc, &tm));\n    char pitch[64] = { 0 };\n    if (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) { strcat(pitch, \"FIXED_PITCH \"); }\n    if (tm.tmPitchAndFamily & TMPF_VECTOR)      { strcat(pitch, \"VECTOR \"); }\n    if (tm.tmPitchAndFamily & TMPF_DEVICE)      { strcat(pitch, \"DEVICE \"); }\n    if (tm.tmPitchAndFamily & TMPF_TRUETYPE)    { strcat(pitch, \"TRUETYPE \"); }\n    rt_println(\"tm: .pitch_and_family: %s\", pitch);\n    rt_println(\".height            : %2d   .ascent (baseline) : %2d  .descent: %2d\",\n            tm.tmHeight, tm.tmAscent, tm.tmDescent);\n    rt_println(\".internal_leading  : %2d   .external_leading  : %2d  .ave_char_width: %2d\",\n            tm.tmInternalLeading, tm.tmExternalLeading, tm.tmAveCharWidth);\n    rt_println(\".max_char_width    : %2d   .weight            : %2d .overhang: %2d\",\n            tm.tmMaxCharWidth, tm.tmWeight, tm.tmOverhang);\n    rt_println(\".digitized_aspect_x: %2d   .digitized_aspect_y: %2d\",\n            tm.tmDigitizedAspectX, tm.tmDigitizedAspectY);\n    rt_swear(tm.tmPitchAndFamily & TMPF_TRUETYPE);\n    OUTLINETEXTMETRICA otm = { .otmSize = sizeof(OUTLINETEXTMETRICA) };\n    uint32_t bytes = GetOutlineTextMetricsA(hdc, otm.otmSize, &otm);\n    rt_swear(bytes == sizeof(OUTLINETEXTMETRICA));\n    // unsupported XHeight CapEmHeight\n    // ignored:    MacDescent, MacLineGap, EMSquare, ItalicAngle\n    //             CharSlopeRise, CharSlopeRun, ItalicAngle\n    rt_println(\"otm: .Ascent       : %2d   .Descent        : %2d\",\n            otm.otmAscent, otm.otmDescent);\n    rt_println(\".otmLineGap        : %2u\", otm.otmLineGap);\n    rt_println(\".FontBox.ltrb      :  %d,%d %2d,%2d\",\n            otm.otmrcFontBox.left, otm.otmrcFontBox.top,\n            otm.otmrcFontBox.right, otm.otmrcFontBox.bottom);\n    rt_println(\".MinimumPPEM       : %2u    (minimum height in pixels)\",\n            otm.otmusMinimumPPEM);\n    rt_println(\".SubscriptOffset   : %d,%d  .SubscriptSize.x   : %dx%d\",\n            otm.otmptSubscriptOffset.x, otm.otmptSubscriptOffset.y,\n            otm.otmptSubscriptSize.x, otm.otmptSubscriptSize.y);\n    rt_println(\".SuperscriptOffset : %d,%d  .SuperscriptSize.x : %dx%d\",\n            otm.otmptSuperscriptOffset.x, otm.otmptSuperscriptOffset.y,\n            otm.otmptSuperscriptSize.x,   otm.otmptSuperscriptSize.y);\n    rt_println(\".UnderscoreSize    : %2d   .UnderscorePosition: %2d\",\n            otm.otmsUnderscoreSize, otm.otmsUnderscorePosition);\n    rt_println(\".StrikeoutSize     : %2u   .StrikeoutPosition : %2d \",\n            otm.otmsStrikeoutSize,  otm.otmsStrikeoutPosition);\n    int32_t h = otm.otmAscent + abs(tm.tmDescent); // without diacritical space above\n    fp32_t pts = (h * 72.0f)  / GetDeviceCaps(hdc, LOGPIXELSY);\n    rt_println(\"height: %.1fpt\", pts);\n}\n\nstatic void ui_gdi_dump_fm(ui_font_t f) {\n    rt_not_null(f);\n    ui_gdi_hdc_with_font(f, { ui_gdi_dump_hdc_fm(hdc); });\n}\n\nstatic void ui_gdi_get_fm(HDC hdc, ui_fm_t* fm) {\n    TEXTMETRICA tm = {0};\n    rt_fatal_win32err(GetTextMetricsA(hdc, &tm));\n    rt_swear(tm.tmPitchAndFamily & TMPF_TRUETYPE);\n    OUTLINETEXTMETRICA otm = { .otmSize = sizeof(OUTLINETEXTMETRICA) };\n    uint32_t bytes = GetOutlineTextMetricsA(hdc, otm.otmSize, &otm);\n    rt_swear(bytes == sizeof(OUTLINETEXTMETRICA));\n    // \"tm.tmAscent\" The ascent (units above the base line) of characters\n    // and actually is \"baseline\" in other terminology\n    // \"otm.otmAscent\" The maximum distance characters in this font extend\n    // above the base line. This is the typographic ascent for the font.\n    // otm.otmEMSquare usually is 2048 which is size of rasterizer\n    fm->height   = tm.tmHeight;\n    fm->baseline = tm.tmAscent;\n    fm->ascent   = otm.otmAscent;\n    fm->descent  = tm.tmDescent;\n    fm->baseline = tm.tmAscent;\n    fm->x_height = otm.otmsXHeight;\n    fm->cap_em_height = otm.otmsCapEmHeight;\n    fm->internal_leading = tm.tmInternalLeading;\n    fm->external_leading = tm.tmExternalLeading;\n    fm->average_char_width = tm.tmAveCharWidth;\n    fm->max_char_width = tm.tmMaxCharWidth;\n    fm->line_gap = otm.otmLineGap;\n    fm->subscript.w = otm.otmptSubscriptSize.x;\n    fm->subscript.h = otm.otmptSubscriptSize.y;\n    fm->subscript_offset.x = otm.otmptSubscriptOffset.x;\n    fm->subscript_offset.y = otm.otmptSubscriptOffset.y;\n    fm->superscript.w = otm.otmptSuperscriptSize.x;\n    fm->superscript.h = otm.otmptSuperscriptSize.y;\n    fm->superscript_offset.x = otm.otmptSuperscriptOffset.x;\n    fm->superscript_offset.y = otm.otmptSuperscriptOffset.y;\n    fm->underscore = otm.otmsUnderscoreSize;\n    fm->underscore_position = otm.otmsUnderscorePosition;\n    fm->strike_through = otm.otmsStrikeoutSize;\n    fm->strike_through_position = otm.otmsStrikeoutPosition;\n    fm->design_units_per_em = (int)otm.otmEMSquare;\n    fm->box = (ui_rect_t){\n                otm.otmrcFontBox.left, otm.otmrcFontBox.top,\n                otm.otmrcFontBox.right - otm.otmrcFontBox.left,\n                otm.otmrcFontBox.top - otm.otmrcFontBox.bottom // inverted\n    };\n    // otm.Descent: The maximum distance characters in this font extend below\n    // the base line. This is the typographic descent for the font.\n    // Negative from the bottom (font.height)\n    // tm.Descent: The descent (units below the base line) of characters.\n    // Positive from the baseline down\n    rt_assert(tm.tmDescent >= 0 && otm.otmDescent <= 0 &&\n           -otm.otmDescent <= tm.tmDescent,\n           \"tm.tmDescent: %d otm.otmDescent: %d\", tm.tmDescent, otm.otmDescent);\n    // \"Mac\" typography is ignored because it's usefulness is unclear.\n    // Italic angle/slant/run is ignored because at the moment edit\n    // view implementation does not support italics and thus does not\n    // need it. Easy to add if necessary.\n}\n\nstatic void ui_gdi_update_fm(ui_fm_t* fm, ui_font_t f) {\n    rt_not_null(f);\n    SIZE em = {0, 0}; // \"m\"\n    *fm = (ui_fm_t){ .font = f };\n//  ui_gdi.dump_fm(f);\n    ui_gdi_hdc_with_font(f, {\n        ui_gdi_get_fm(hdc, fm);\n        // rt_glyph_nbsp and \"M\" have the same result\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"m\", 1, &em));\n        SIZE vl = {0}; // \"|\" Vertical Line https://www.compart.com/en/unicode/U+007C\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"|\", 1, &vl));\n        SIZE e3 = {0}; // Three-Em Dash\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc,\n            rt_glyph_three_em_dash, 1, &e3));\n        fm->mono = em.cx == vl.cx && vl.cx == e3.cx;\n//      rt_println(\"vl: %d %d\", vl.cx, vl.cy);\n//      rt_println(\"e3: %d %d\", e3.cx, e3.cy);\n//      rt_println(\"fm->mono: %d height: %d baseline: %d ascent: %d descent: %d\",\n//              fm->mono, fm->height, fm->baseline, fm->ascent, fm->descent);\n    });\n    rt_assert(fm->baseline <= fm->height);\n    fm->em = (ui_wh_t){ .w = fm->height, .h = fm->height };\n//  rt_println(\"fm.em: %dx%d\", fm->em.w, fm->em.h);\n}\n\nstatic int32_t ui_gdi_draw_utf16(ui_font_t font, const char* s, int32_t n,\n        RECT* r, uint32_t format) { // ~70 microsecond Core i-7 3667U 2.0 GHz (2012)\n    // if font == null, draws on HDC with selected font\nif (0) {\n    HDC hdc = ui_gdi_hdc();\n    if (hdc != null) {\n        SIZE em = {0, 0}; // \"M\"\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"M\", 1, &em));\n        rt_println(\"em: %d %d\", em.cx, em.cy);\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, rt_glyph_em_quad, 1, &em));\n        rt_println(\"em: %d %d\", em.cx, em.cy);\n        SIZE vl = {0}; // \"|\" Vertical Line https://www.compart.com/en/unicode/U+007C\n        SIZE e3 = {0}; // Three-Em Dash\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, \"|\", 1, &vl));\n        rt_println(\"vl: %d %d\", vl.cx, vl.cy);\n        rt_fatal_win32err(GetTextExtentPoint32A(hdc, rt_glyph_three_em_dash, 1, &e3));\n        rt_println(\"e3: %d %d\", e3.cx, e3.cy);\n    }\n}\n    int32_t count = rt_str.utf16_chars(s, -1);\n    rt_assert(0 < count && count < 4096, \"be reasonable count: %d?\", count);\n    uint16_t ws[4096];\n    rt_swear(count <= rt_countof(ws), \"find another way to draw!\");\n    rt_str.utf8to16(ws, count, s, -1);\n    int32_t h = 0; // return value is the height of the text\n    if (font != null) {\n        ui_gdi_hdc_with_font(font, { h = DrawTextW(hdc, ws, n, r, format); });\n    } else { // with already selected font\n        ui_gdi_with_hdc({ h = DrawTextW(hdc, ws, n, r, format); });\n    }\n    return h;\n}\n\ntypedef struct { // draw text parameters\n    const ui_fm_t* fm;\n    const char* format; // format string\n    va_list va;\n    RECT rc;\n    uint32_t flags; // flags:\n    // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-drawtextw\n    // DT_CALCRECT DT_NOCLIP useful for measure\n    // DT_END_ELLIPSIS useful for clipping\n    // DT_LEFT, DT_RIGHT, DT_CENTER useful for paragraphs\n    // DT_WORDBREAK is not good (GDI does not break nicely)\n    // DT_BOTTOM, DT_VCENTER limited usability in weird cases (layout is better)\n    // DT_NOPREFIX not to draw underline at \"&Keyboard shortcuts\n    // DT_SINGLELINE versus multiline\n} ui_gdi_dtp_t;\n\nstatic void ui_gdi_text_draw(ui_gdi_dtp_t* p) {\n    rt_not_null(p);\n    char text[4096]; // expected to be enough for single text draw\n    text[0] = 0;\n    rt_str.format_va(text, rt_countof(text), p->format, p->va);\n    text[rt_countof(text) - 1] = 0;\n    int32_t k = (int32_t)rt_str.len(text);\n    if (k > 0) {\n        rt_swear(k > 0 && k < rt_countof(text), \"k=%d n=%d fmt=%s\", k, p->format);\n        // rectangle is always calculated - it makes draw text\n        // much slower but UI layer is mostly uses bitmap caching:\n        if ((p->flags & DT_CALCRECT) == 0) {\n            // no actual drawing just calculate rectangle\n            bool b = ui_gdi_draw_utf16(p->fm->font, text, -1, &p->rc, p->flags | DT_CALCRECT);\n            rt_assert(b, \"text_utf16(%s) failed\", text); (void)b;\n        }\n        bool b = ui_gdi_draw_utf16(p->fm->font, text, -1, &p->rc, p->flags);\n        rt_assert(b, \"text_utf16(%s) failed\", text); (void)b;\n    } else {\n        p->rc.right = p->rc.left;\n        p->rc.bottom = p->rc.top + p->fm->height;\n    }\n}\n\nenum {\n    sl_draw          = DT_LEFT|DT_NOCLIP|DT_SINGLELINE|DT_NOCLIP,\n    sl_measure       = sl_draw|DT_CALCRECT,\n    ml_draw_break    = DT_LEFT|DT_NOPREFIX|DT_NOCLIP|DT_NOFULLWIDTHCHARBREAK|\n                       DT_WORDBREAK,\n    ml_measure_break = ml_draw_break|DT_CALCRECT,\n    ml_draw          = DT_LEFT|DT_NOPREFIX|DT_NOCLIP|DT_NOFULLWIDTHCHARBREAK,\n    ml_measure       = ml_draw|DT_CALCRECT\n};\n\nstatic ui_wh_t ui_gdi_text_with_flags(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w,\n        const char* format, va_list va, uint32_t flags) {\n    const int32_t right = w == 0 ? 0 : x + w;\n    ui_gdi_dtp_t p = {\n        .fm = ta->fm,\n        .format = format,\n        .va = va,\n        .rc = {.left = x, .top = y, .right = right, .bottom = 0 },\n        .flags = flags\n    };\n    ui_color_t c = ta->color;\n    if (!ta->measure) {\n        if (ui_color_is_undefined(c)) {\n            rt_swear(ta->color_id > 0);\n            c = ui_colors.get_color(ta->color_id);\n        } else {\n            rt_swear(ta->color_id == 0);\n        }\n        c = ui_gdi_set_text_color(c);\n    }\n    ui_gdi_text_draw(&p);\n    if (!ta->measure) { ui_gdi_set_text_color(c); } // restore color\n    return (ui_wh_t){ p.rc.right - p.rc.left, p.rc.bottom - p.rc.top };\n}\n\nstatic ui_wh_t ui_gdi_text_va(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y,  const char* format, va_list va) {\n    const uint32_t flags = sl_draw | (ta->measure ? sl_measure : 0);\n    return ui_gdi_text_with_flags(ta, x, y, 0, format, va, flags);\n}\n\nstatic ui_wh_t ui_gdi_text(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, const char* format, ...) {\n    const uint32_t flags = sl_draw | (ta->measure ? sl_measure : 0);\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_gdi_text_with_flags(ta, x, y, 0, format, va, flags);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_gdi_multiline_va(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w, const char* format, va_list va) {\n    const uint32_t flags = ta->measure ?\n                            (w <= 0 ? ml_measure : ml_measure_break) :\n                            (w <= 0 ? ml_draw    : ml_draw_break);\n    return ui_gdi_text_with_flags(ta, x, y, w, format, va, flags);\n}\n\nstatic ui_wh_t ui_gdi_multiline(const ui_gdi_ta_t* ta,\n        int32_t x, int32_t y, int32_t w, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_gdi_multiline_va(ta, x, y, w, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_gdi_glyphs_placement(const ui_gdi_ta_t* ta,\n        const char* utf8, int32_t bytes, int32_t x[], int32_t glyphs) {\n    rt_swear(bytes >= 0 && glyphs >= 0 && glyphs <= bytes);\n    rt_assert(false, \"Does not work for Tamil simplest utf8: \\xe0\\xae\\x9a utf16: 0x0B9A\");\n    x[0] = 0;\n    ui_wh_t wh = { .w = 0, .h = 0 };\n    if (bytes > 0) {\n        const int32_t chars = rt_str.utf16_chars(utf8, bytes);\n        uint16_t* utf16 = rt_stackalloc((chars + 1) * sizeof(uint16_t));\n        uint16_t* output = rt_stackalloc((chars + 1) * sizeof(uint16_t));\n        const errno_t r = rt_str.utf8to16(utf16, chars, utf8, bytes);\n        rt_swear(r == 0);\n// TODO: remove\n#if 1\n        char str[16 * 1024] = {0};\n        char hex[16 * 1024] = {0};\n        for (int i = 0; i < chars; i++) {\n            rt_str_printf(hex, \"%04X \", utf16[i]);\n            strcat(str, hex);\n        }\nrt_println(\"%.*s %s %p bytes:%d glyphs:%d font:%p hdc:%p\", bytes, utf8, str, utf8, bytes, glyphs, ta->fm->font, ui_gdi_context.hdc);\n#endif\n        GCP_RESULTSW gcp = {\n            .lStructSize = sizeof(GCP_RESULTSW),\n            .lpOutString = output,\n            .nGlyphs = glyphs\n        };\n        gcp.lpDx = (int*)rt_stackalloc((chars + 1) * sizeof(int));\n        DWORD n = 0;\n        const int mx = INT32_MAX; // max extent\n        const DWORD f = GCP_MAXEXTENT; // |GCP_GLYPHSHAPE|GCP_DIACRITIC|GCP_LIGATE\n        if (ta->fm->font != null) {\n            ui_gdi_hdc_with_font(ta->fm->font, {\n                n = GetCharacterPlacementW(hdc, utf16, chars, mx, &gcp, f);\n            });\n        } else { // with already selected font\n            ui_gdi_with_hdc({\n                n = GetCharacterPlacementW(hdc, utf16, chars, mx, &gcp, f);\n            });\n        }\n        wh = (ui_wh_t){ .w = LOWORD(n), .h = HIWORD(n) };\n        if (n != 0) {\n            // IS_HIGH_SURROGATE(wch)\n            // IS_LOW_SURROGATE(wch)\n            // IS_SURROGATE_PAIR(hs, ls)\n            int32_t i = 0;\n            int32_t k = 1;\n            while (i < chars) {\n                x[k] = x[k - 1] + gcp.lpDx[i];\n//              rt_println(\"%d\", x[i]);\n                k++;\n                if (i < chars - 1 && rt_str.utf16_is_high_surrogate(utf16[i]) &&\n                                     rt_str.utf16_is_low_surrogate(utf16[i + 1])) {\n                    i += 2;\n                } else {\n                    i++;\n                }\n            }\n            rt_assert(k == glyphs + 1);\n        } else {\n//          rt_assert(false, \"GetCharacterPlacementW() failed\");\n            rt_println(\"GetCharacterPlacementW() failed\");\n        }\n    }\n    return wh;\n}\n\n// to enable load_bitmap() function\n// 1. Add\n//    curl.exe https://raw.githubusercontent.com/nothings/stb/master/stb_bitmap.h stb_bitmap.h\n//    to the project precompile build step\n// 2. After\n//    #define ui_implementation\n//    include \"ui/ui.h\"\n//    add\n//    #define STBI_ASSERT(x) assert(x)\n//    #define STB_bitmap_IMPLEMENTATION\n//    #include \"stb_bitmap.h\"\n\nstatic uint8_t* ui_gdi_load_bitmap(const void* data, int32_t bytes, int* w, int* h,\n        int* bytes_per_pixel, int32_t preferred_bytes_per_pixel) {\n    #ifdef STBI_VERSION\n        return stbi_load_from_memory((uint8_t const*)data, bytes, w, h,\n            bytes_per_pixel, preferred_bytes_per_pixel);\n    #else // see instructions above\n        (void)data; (void)bytes; (void)data; (void)w; (void)h;\n        (void)bytes_per_pixel; (void)preferred_bytes_per_pixel;\n        rt_fatal_if(true, \"curl.exe --silent --fail --create-dirs \"\n            \"https://raw.githubusercontent.com/nothings/stb/master/stb_bitmap.h \"\n            \"--output ext/stb_bitmap.h\");\n        return null;\n    #endif\n}\n\nstatic void ui_gdi_bitmap_dispose(ui_bitmap_t* image) {\n    rt_fatal_win32err(DeleteBitmap(image->texture));\n    memset(image, 0, sizeof(ui_bitmap_t));\n}\n\nui_gdi_if ui_gdi = {\n    .ta = {\n        .prop = {\n            .normal = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.normal,\n                .measure  = false\n            },\n            .title = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.title,\n                .measure  = false\n            },\n            .rubric = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.rubric,\n                .measure  = false\n            },\n            .H1 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H1,\n                .measure  = false\n            },\n            .H2 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H2,\n                .measure  = false\n            },\n            .H3 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.prop.H3,\n                .measure  = false\n            }\n        },\n        .mono = {\n            .normal = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.normal,\n                .measure  = false\n            },\n            .title = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.title,\n                .measure  = false\n            },\n            .rubric = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.rubric,\n                .measure  = false\n            },\n            .H1 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H1,\n                .measure  = false\n            },\n            .H2 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H2,\n                .measure  = false\n            },\n            .H3 = {\n                .color_id = ui_color_id_window_text,\n                .color    = ui_color_undefined,\n                .fm       = &ui_app.fm.mono.H3,\n                .measure  = false\n            }\n        },\n    },\n    .init                     = ui_gdi_init,\n    .begin                    = ui_gdi_begin,\n    .end                      = ui_gdi_end,\n    .color_rgb                = ui_gdi_color_rgb,\n    .bitmap_init              = ui_gdi_bitmap_init,\n    .bitmap_init_rgbx         = ui_gdi_bitmap_init_rgbx,\n    .bitmap_dispose           = ui_gdi_bitmap_dispose,\n    .alpha                    = ui_gdi_alpha,\n    .bitmap                   = ui_gdi_bitmap,\n    .icon                     = ui_gdi_icon,\n    .set_clip                 = ui_gdi_set_clip,\n    .pixel                    = ui_gdi_pixel,\n    .line                     = ui_gdi_line,\n    .frame                    = ui_gdi_frame,\n    .rect                     = ui_gdi_rect,\n    .fill                     = ui_gdi_fill,\n    .poly                     = ui_gdi_poly,\n    .circle                   = ui_gdi_circle,\n    .rounded                  = ui_gdi_rounded,\n    .gradient                 = ui_gdi_gradient,\n    .pixels                   = ui_gdi_pixels,\n    .greyscale                = ui_gdi_greyscale,\n    .bgr                      = ui_gdi_bgr,\n    .bgrx                     = ui_gdi_bgrx,\n    .cleartype                = ui_gdi_cleartype,\n    .font_smoothing_contrast  = ui_gdi_font_smoothing_contrast,\n    .create_font              = ui_gdi_create_font,\n    .font                     = ui_gdi_font,\n    .delete_font              = ui_gdi_delete_font,\n    .dump_fm                  = ui_gdi_dump_fm,\n    .update_fm                = ui_gdi_update_fm,\n    .text_va                  = ui_gdi_text_va,\n    .text                     = ui_gdi_text,\n    .multiline_va             = ui_gdi_multiline_va,\n    .multiline                = ui_gdi_multiline,\n    .glyphs_placement         = ui_gdi_glyphs_placement,\n    .fini                     = ui_gdi_fini\n};\n\n#pragma pop_macro(\"ui_gdi_hdc_with_font\")\n#pragma pop_macro(\"ui_gdi_with_hdc\")\n"
  },
  {
    "path": "src/ui/ui_image.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic fp64_t ui_image_scale_of(int32_t nominator, int32_t denominator) {\n    const int32_t zn = 1 << (nominator - 1);\n    const int32_t zd = 1 << (denominator - 1);\n    return (fp64_t)zn / (fp64_t)zd;\n}\n\nstatic fp64_t ui_image_scale(ui_image_t* iv) {\n    if (iv->fit && iv->w > 0 && iv->h > 0) {\n        return min((fp64_t)iv->w / iv->image.w,\n                   (fp64_t)iv->h / iv->image.h);\n    } else if (iv->fill && iv->w > 0 && iv->h > 0) {\n        return max((fp64_t)iv->w / iv->image.w,\n                   (fp64_t)iv->h / iv->image.h);\n    } else {\n        return ui_image_scale_of(iv->zn, iv->zd);\n    }\n}\n\nstatic ui_rect_t ui_image_position(ui_image_t* iv) {\n    ui_rect_t rc = { 0, 0, 0, 0 };\n    if (iv->image.pixels != null) {\n        int32_t iw = iv->image.w;\n        int32_t ih = iv->image.h;\n        // zoomed image width and height\n        rc.w = (int32_t)((fp64_t)iw * ui_image.scale(iv));\n        rc.h = (int32_t)((fp64_t)ih * ui_image.scale(iv));\n        int32_t shift_x = (int32_t)((rc.w - iv->w) * iv->sx);\n        int32_t shift_y = (int32_t)((rc.h - iv->h) * iv->sy);\n        // shift_x and shift_y are in zoomed image coordinates\n        rc.x = iv->x - shift_x; // screen x\n        rc.y = iv->y - shift_y; // screen y\n    }\n    return rc;\n}\n\nstatic void ui_image_paint(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n//  ui_gdi.fill(v->x, v->y, v->w, v->h, ui_colors.black);\n    if (iv->image.pixels != null) {\n        ui_gdi.set_clip(v->x, v->y, v->w, v->h);\n        rt_swear(!iv->fit || !iv->fill, \"make up your mind\");\n        rt_swear(0 < iv->zn && iv->zn <= 16);\n        rt_swear(0 < iv->zd && iv->zd <= 16);\n        // only 1:2 and 2:1 etc are supported:\n        if (iv->zn != 1) { rt_swear(iv->zd == 1); }\n        if (iv->zd != 1) { rt_swear(iv->zn == 1); }\n        const int32_t iw = iv->image.w;\n        const int32_t ih = iv->image.h;\n        ui_rect_t rc = ui_image_position(iv);\n        if (iv->image.bpp == 1) {\n            ui_gdi.greyscale(rc.x, rc.y, rc.w, rc.h,\n                0, 0, iw, ih,\n                iw, ih, iv->image.stride,\n                iv->image.pixels);\n        } else if (iv->image.bpp == 3) {\n            ui_gdi.bgr(rc.x, rc.y, rc.w, rc.h,\n                         0, 0, iw, ih,\n                         iw, ih, iv->image.stride,\n                         iv->image.pixels);\n        } else if (iv->image.bpp == 4) {\n            if (iv->image.texture == null) {\n                ui_gdi.bgrx(rc.x, rc.y, rc.w, rc.h,\n                              0, 0, iw, ih,\n                              iw, ih, iv->image.stride,\n                              iv->image.pixels);\n            } else {\n                ui_gdi.alpha(rc.x, rc.y, rc.w, rc.h,\n                              0, 0, iw, ih,\n                              &iv->image, iv->alpha);\n            }\n        } else {\n            rt_swear(false, \"unsupported .c: %d\", iv->image.bpp);\n        }\n        if (ui_view.has_focus(v)) {\n            ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n            ui_gdi.frame(v->x, v->y, v->w, v->h, highlight);\n        }\n        ui_gdi.set_clip(0, 0, 0, 0);\n    }\n}\n\nstatic void ui_image_tools_background(ui_view_t* v) {\n    ui_color_t face = ui_colors.get_color(ui_color_id_button_face);\n    ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n    ui_gdi.fill(v->x, v->y, v->w, v->h, face);\n    ui_gdi.frame(v->x, v->y, v->w, v->h, highlight);\n}\n\nstatic void ui_image_show_tools(ui_image_t* iv, bool show) {\n    if (iv->focusable) {\n        if (iv->tool.bar.state.hidden  != !show) {\n            iv->tool.bar.state.hidden   = !show;\n            iv->tool.bar.state.disabled = !show;\n            iv->tool.ratio.state.hidden = !show;\n            ui_app.request_layout();\n        }\n        if (show) { // hide in 3.3 seconds:\n            iv->when = rt_clock.seconds() + 3.3;\n        } else {\n            iv->when = 0;\n        }\n    }\n}\n\nstatic void ui_image_fit_fill_scale(ui_image_t* iv) {\n    fp64_t s = ui_image.scale(iv);\n    rt_assert(s != 0);\n    if (s > 1) {\n        ui_view.set_text(&iv->tool.ratio, \"1:%.3f\", s);\n    } else if (s != 0 && s <= 1) {\n        ui_view.set_text(&iv->tool.ratio, \"%.3f:1\", 1.0 / s);\n    } else {\n        // s should not be zero ever\n    }\n}\n\nstatic void ui_image_measure(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (!v->focusable) {\n        v->w = (int32_t)(iv->image.w * ui_image.scale(iv));\n        v->h = (int32_t)(iv->image.h * ui_image.scale(iv));\n        if (iv->fit || iv->fill) {\n            ui_image_fit_fill_scale(iv);\n        }\n    } else {\n        v->w = 0;\n        v->h = 0;\n    }\n}\n\nstatic void ui_image_layout(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (iv->fit || iv->fill) {\n        ui_image_fit_fill_scale(iv);\n        ui_view.measure_control(&iv->tool.ratio);\n    }\n    iv->tool.bar.x = v->x + v->w - iv->tool.bar.w;\n    iv->tool.bar.y = v->y;\n    iv->tool.ratio.x = v->x + v->w - iv->tool.ratio.w;\n    iv->tool.ratio.y = v->y + v->h - iv->tool.ratio.h;\n}\n\nstatic void ui_image_every_100ms(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (iv->when != 0 && rt_clock.seconds() > iv->when) {\n        ui_image_show_tools(iv, false);\n    }\n}\n\nstatic void ui_image_focus_lost(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    ui_image_show_tools(iv, ui_view.has_focus(v));\n}\n\nstatic void ui_image_focus_gained(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    ui_image_show_tools(iv, ui_view.has_focus(v));\n}\n\nstatic void ui_image_zoomed(ui_image_t* iv) {\n    iv->fill = false;\n    iv->fit  = false;\n    // 0=16:1 1=8:1 2=4:1 3=2:1 4=1:1 5=1:2 6=1:4 7=1:8 8=1:16\n    int32_t n  = iv->zoom - 4;\n    int32_t zn = iv->zn;\n    int32_t zd = iv->zd;\n    fp64_t scale_before = ui_image.scale(iv);\n    if (n > 0) {\n        zn = n + 1;\n        zd = 1;\n    } else if (n < 0) {\n        zn = 1;\n        zd = -n + 1;\n    } else if (n == 0) {\n        zn = 1;\n        zd = 1;\n    }\n    fp64_t scale_after = ui_image_scale_of(zn, zd);\n    if (scale_after != scale_before) {\n        iv->zn = zn;\n        iv->zd = zd;\n        const int32_t nm = 1 << (iv->zn - 1);\n        const int32_t dm = 1 << (iv->zd - 1);\n        ui_view.set_text(&iv->tool.ratio, \"%d:%d\", nm, dm);\n    }\n    if (iv->zn == 1) {\n        iv->zoom = 4 - (iv->zd - 1);\n    } else if (iv->zd == 1) {\n        iv->zoom = 4 + (iv->zn - 1);\n    } else {\n        rt_swear(false);\n    }\n    // is whole image visible?\n    fp64_t s = ui_image.scale(iv);\n    bool whole = (int32_t)(iv->image.w * s) <= iv->w &&\n                 (int32_t)(iv->image.h * s) <= iv->h;\n    if (whole) { iv->sx = 0.5; iv->sy = 0.5; }\n    ui_view.invalidate(&iv->view, null);\n    ui_image_show_tools(iv, true);\n}\n\nstatic void ui_image_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    fp64_t dx = (fp64_t)dx_dy.x;\n    fp64_t dy = (fp64_t)dx_dy.y;\n    ui_image_t* iv = (ui_image_t*)v;\n    if (ui_view.has_focus(v)) {\n        fp64_t s = ui_image.scale(iv);\n        if (iv->image.w * s > iv->w || iv->image.h * s > iv->h) {\n            iv->sx = max(0.0, min(iv->sx + dx / iv->image.w, 1.0));\n        } else {\n            iv->sx = 0.5;\n        }\n        if (iv->image.h * s > iv->h) {\n            iv->sy = max(0.0, min(iv->sy + dy / iv->image.h, 1.0));\n        } else {\n            iv->sy = 0.5;\n        }\n        ui_view.invalidate(&iv->view, null);\n    }\n}\n\nstatic bool ui_image_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false;\n    if (v->focusable) {\n        ui_image_t* iv = (ui_image_t*)v;\n        const int32_t x = ui_app.mouse.x - iv->x;\n        const int32_t y = ui_app.mouse.y - iv->y;\n        bool tools  = !iv->tool.bar.state.hidden &&\n                      ui_view.inside(&iv->tool.bar, &ui_app.mouse);\n        bool inside = ui_view.inside(&iv->view, &ui_app.mouse) && !tools;\n        bool left   = ix == 0;\n        bool drag_started = iv->drag_start.x >= 0 && iv->drag_start.y >= 0;\n        if (left && inside && !drag_started) {\n            iv->drag_start = (ui_point_t){x, y};\n        }\n        if (!pressed) {\n            iv->drag_start = (ui_point_t){-1, -1};\n        }\n        swallow = inside || tools;\n    }\n//  rt_println(\"inside %s\", inside ? \"true\" : \"false\");\n    return swallow;\n}\n\nstatic bool ui_image_mouse_move(ui_view_t* v) {\n    ui_image_t* iv = (ui_image_t*)v;\n    bool drag_started = iv->drag_start.x >= 0 && iv->drag_start.y >= 0;\n    bool tools  = !iv->tool.bar.state.hidden &&\n                  ui_view.inside(&iv->tool.bar, &ui_app.mouse);\n    bool inside = ui_view.inside(&iv->view, &ui_app.mouse) && !tools;\n    if (drag_started && inside) {\n        ui_image_show_tools(iv, false);\n        const int32_t x = ui_app.mouse.x - iv->x;\n        const int32_t y = ui_app.mouse.y - iv->y;\n        ui_point_t dx_dy = {iv->drag_start.x - x, iv->drag_start.y - y};\n        ui_image_mouse_scroll(v, dx_dy);\n        iv->drag_start = (ui_point_t){x, y};\n    } else if (inside) {\n        ui_image_show_tools(iv, true);\n    } else if (!inside && !tools) {\n        ui_image_show_tools(iv, false);\n    }\n//  rt_println(\"inside %s\", inside ? \"true\" : \"false\");\n    return inside;\n}\n\nstatic bool ui_image_key_pressed(ui_view_t* v, int64_t vk) {\n    ui_image_t* iv = (ui_image_t*)v;\n    bool swallowed = false;\n    if (ui_view.has_focus(v)) {\n        swallowed = true;\n        if (vk == ui.key.up) {\n            ui_image_mouse_scroll(v, (ui_point_t){0, -iv->h / 8});\n        } else if (vk == ui.key.down) {\n            ui_image_mouse_scroll(v, (ui_point_t){0, +iv->h / 8});\n        } else if (vk == ui.key.left) {\n            ui_image_mouse_scroll(v, (ui_point_t){-iv->w / 8, 0});\n        } else if (vk == ui.key.right) {\n            ui_image_mouse_scroll(v, (ui_point_t){+iv->w / 8, 0});\n        } else if (vk == ui.key.plus) {\n            if (iv->zoom < 8) {\n                iv->zoom++;\n                ui_image_zoomed(iv);\n            }\n        } else if (vk == ui.key.minus) {\n            if (iv->zoom > 0) {\n                iv->zoom--;\n                ui_image_zoomed(iv);\n            }\n        } else {\n            swallowed = false;\n        }\n    }\n    return swallowed;\n}\n\nstatic void ui_image_zoom_in(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    if (iv->zoom < 8) {\n        iv->zoom++;\n        ui_image_zoomed(iv);\n    }\n}\n\nstatic void ui_image_zoom_out(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    if (iv->zoom > 0) {\n        iv->zoom--;\n        ui_image_zoomed(iv);\n    }\n}\n\nstatic void ui_image_fit(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->fit  = true;\n    iv->fill = false;\n    ui_image_fit_fill_scale(iv);\n    ui_view.invalidate(&iv->view, null);\n}\n\nstatic void ui_image_fill(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->fill = true;\n    iv->fit  = false;\n    ui_image_fit_fill_scale(iv);\n    ui_view.invalidate(&iv->view, null);\n}\n\nstatic void ui_image_zoom_1t1(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    iv->zoom = 4;\n    ui_image_zoomed(iv);\n}\n\nstatic ui_label_t ui_image_about = ui_label(0,\n    \"Keyboard shortcuts:\\n\\n\"\n    \"Ctrl+C copies image to the clipboard.\\n\\n\"\n    rt_glyph_heavy_plus_sign \" zoom in; \"\n    rt_glyph_heavy_minus_sign \" zoom out;\\n\"\n    rt_glyph_open_circle_arrows_one_overlay \" 1:1.\\n\\n\"\n    rt_glyph_up_down_arrow \" Fit;\\n\"\n    rt_glyph_left_right_arrow \" Fill.\\n\\n\"\n    \"Left/Right Arrows \"\n    rt_glyph_leftward_arrow\n    rt_glyph_rightwards_arrow\n    \"Up/Down Arrows \"\n    rt_glyph_upwards_arrow\n    rt_glyph_downwards_arrow\n    \"\\npans the image inside view.\\n\\n\"\n    \"Mouse wheel or mouse / touchpad hold and drag to pan.\\n\"\n);\n\nstatic void ui_image_help(ui_button_t* rt_unused(b)) {\n    ui_app.show_toast(&ui_image_about, 7.0);\n}\n\nstatic void ui_image_copy_to_clipboard(ui_image_t* iv) {\n    ui_bitmap_t image = {0};\n    if (iv->image.texture != null) {\n        rt_clipboard.put_image(&iv->image);\n    } else {\n        ui_gdi.bitmap_init(&image, iv->image.w, iv->image.h,\n                                  iv->image.bpp, iv->image.pixels);\n        rt_clipboard.put_image(&image);\n        ui_gdi.bitmap_dispose(&image);\n    }\n    static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n    ui_app.show_hint(&hint, ui_app.mouse.x,\n                            ui_app.mouse.y + iv->fm->height,\n                     1.5);\n}\n\nstatic void ui_image_copy(ui_button_t* b) {\n    ui_image_t* iv = (ui_image_t*)b->that;\n    ui_image_copy_to_clipboard(iv);\n}\n\nstatic void ui_image_character(ui_view_t* v, const char* utf8) {\n    ui_image_t* iv = (ui_image_t*)v;\n    if (ui_view.has_focus(v)) { // && ui_app.ctrl ?\n        char ch = utf8[0];\n        if (ch == '+' || ch == '=') {\n            if (iv->zoom < 8) {\n                iv->zoom++;\n                ui_image_zoomed(iv);\n            }\n        } else if (ch == '-' || ch == '_') {\n            if (iv->zoom > 0) {\n                iv->zoom--;\n                ui_image_zoomed(iv);\n            }\n        } else if (ch == '<' || ch == ',') {\n            ui_image_mouse_scroll(v, (ui_point_t){-iv->w / 8, 0});\n        } else if (ch == '>' || ch == '.') {\n            ui_image_mouse_scroll(v, (ui_point_t){+iv->w / 8, 0});\n        } else if (ch == '0') {\n            iv->zoom = 4;\n            ui_image_zoomed(iv);\n        } else if (ch == 3 && iv->image.pixels != null) { // Ctrl+C\n            ui_image_copy_to_clipboard(iv);\n        }\n    }\n}\n\nstatic void ui_image_add_button(ui_image_t* iv, ui_button_t* b,\n    const char* label, void (*cb)(ui_button_t* b), const char* hint) {\n    *b = (ui_button_t)ui_button(\"\", 0.0f, cb);\n    ui_view.set_text(b, label);\n    b->that = iv;\n    b->insets.top = 0;\n    b->insets.bottom = 0;\n    b->padding.top = 0;\n    b->padding.bottom = 0;\n    b->insets  = (ui_margins_t){0};\n    b->padding = (ui_margins_t){0};\n    b->flat = true;\n    b->fm = &ui_app.fm.mono.normal;\n    b->min_w_em = 1.5f;\n    rt_str_printf(b->hint, \"%s\", hint);\n    ui_view.add_last(&iv->tool.bar, b);\n}\n\nvoid ui_image_init(ui_image_t* iv) {\n    memset(iv, 0x00, sizeof(*iv));\n    iv->type         = ui_view_image;\n    iv->paint        = ui_image_paint;\n    iv->tap          = ui_image_tap;\n    iv->mouse_move   = ui_image_mouse_move;\n    iv->measure      = ui_image_measure;\n    iv->layout       = ui_image_layout;\n    iv->every_100ms  = ui_image_every_100ms;\n    iv->focus_lost   = ui_image_focus_lost;\n    iv->focus_gained = ui_image_focus_gained;\n    iv->mouse_scroll = ui_image_mouse_scroll;\n    iv->character    = ui_image_character;\n    iv->key_pressed  = ui_image_key_pressed;\n    iv->fm           = &ui_app.fm.prop.normal;\n    iv->tool.bar = (ui_view_t)ui_view(span);\n    // buttons:\n    ui_image_add_button(iv, &iv->tool.copy, \"\\xF0\\x9F\\x93\\x8B\", ui_image_copy,\n        \"Copy to Clipboard Ctrl+C\");\n    ui_image_add_button(iv, &iv->tool.zoom_out,\n                    rt_glyph_heavy_minus_sign,\n                    ui_image_zoom_out, \"Zoom Out\");\n    ui_image_add_button(iv, &iv->tool.zoom_1t1,\n                    rt_glyph_open_circle_arrows_one_overlay,\n                    ui_image_zoom_1t1, \"Reset to 1:1\");\n    ui_image_add_button(iv, &iv->tool.zoom_in,\n                     rt_glyph_heavy_plus_sign,\n                     ui_image_zoom_in,  \"Zoom In\");\n    ui_image_add_button(iv, &iv->tool.fit,\n                     rt_glyph_up_down_arrow,\n                     ui_image_fit,  \"Fit\");\n    ui_image_add_button(iv, &iv->tool.fill,\n                     rt_glyph_left_right_arrow,\n                     ui_image_fill,  \"Fill\");\n    ui_image_add_button(iv, &iv->tool.help,\n                     \"?\", ui_image_help, \"Help\");\n    iv->tool.zoom_1t1.min_w_em = 1.25f;\n    iv->tool.ratio = (ui_label_t)ui_label(0, \"1:1\");\n    iv->tool.ratio.color = ui_colors.get_color(ui_color_id_highlight);\n    iv->tool.ratio.color_id = ui_color_id_highlight;\n    ui_view.add_last(&iv->view, &iv->tool.bar);\n    ui_view.add_last(&iv->view, &iv->tool.ratio);\n    iv->tool.bar.state.hidden = true;\n    iv->tool.ratio.state.hidden = true;\n    iv->tool.bar.erase   = ui_image_tools_background;\n    iv->tool.ratio.erase = ui_image_tools_background;\n    iv->zoom = 4;\n    iv->zn = 1;\n    iv->zd = 1;\n    iv->sx = 0.5;\n    iv->sy = 0.5;\n    iv->drag_start = (ui_point_t){-1, -1};\n    iv->debug.id = \"#image\";\n}\n\nvoid ui_image_init_with(ui_image_t* iv, const uint8_t* pixels,\n                                  int32_t w, int32_t h,\n                                  int32_t c, int32_t s) {\n    ui_image_init(iv);\n    iv->image.pixels = (uint8_t*)pixels;\n    iv->image.w = w;\n    iv->image.h = h;\n    iv->image.bpp = c;\n    iv->image.stride = s;\n}\n\nstatic void ui_image_ratio(ui_image_t* iv, int32_t zn, int32_t zd) {\n    rt_swear(0 < zn && zn <= 16);\n    rt_swear(0 < zd && zd <= 16);\n    // only 1:2 and 2:1 etc are supported:\n    if (zn != 1) { rt_swear(zd == 1); }\n    if (zd != 1) { rt_swear(zn == 1); }\n    iv->zn = zn;\n    iv->zd = zd;\n    iv->fit  = false;\n    iv->fill = false;\n}\n\nui_image_if ui_image = {\n    .init      = ui_image_init,\n    .init_with = ui_image_init_with,\n    .ratio     = ui_image_ratio,\n    .scale     = ui_image_scale,\n    .position  = ui_image_position\n};\n\n"
  },
  {
    "path": "src/ui/ui_label.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic void ui_label_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_label);\n    rt_assert(!ui_view.is_hidden(v));\n    const char* s = ui_view.string(v);\n    ui_color_t c = v->state.hover && v->highlightable ?\n        ui_colors.interpolate(v->color, ui_colors.blue, 1.0f / 8.0f) :\n        v->color;\n    const int32_t tx = v->x + v->text.xy.x;\n    const int32_t ty = v->y + v->text.xy.y;\n    const ui_gdi_ta_t ta = { .fm = v->fm, .color = c };\n    const bool multiline = strchr(s, '\\n') != null;\n    if (multiline) {\n        int32_t w = (int32_t)((fp64_t)v->min_w_em * (fp64_t)v->fm->em.w + 0.5);\n        ui_gdi.multiline(&ta, tx, ty, w, \"%s\", ui_view.string(v));\n    } else {\n        ui_gdi.text(&ta, tx, ty, \"%s\", ui_view.string(v));\n    }\n    if (v->state.hover && !v->flat && v->highlightable) {\n        ui_color_t highlight = ui_colors.get_color(ui_color_id_highlight);\n        int32_t radius = (v->fm->em.h / 4) | 0x1; // corner radius\n        int32_t h = multiline ? v->h : v->fm->baseline + v->fm->descent;\n        ui_gdi.rounded(v->x - radius, v->y, v->w + 2 * radius, h,\n                       radius, highlight, ui_colors.transparent);\n    }\n}\n\nstatic bool ui_label_context_menu(ui_view_t* v) {\n    rt_assert(!ui_view.is_hidden(v) && !ui_view.is_disabled(v));\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        rt_clipboard.put_text(ui_view.string(v));\n        static ui_label_t hint = ui_label(0.0f, \"copied to clipboard\");\n        int32_t x = v->x + v->w / 2;\n        int32_t y = v->y + v->h;\n        ui_app.show_hint(&hint, x, y, 0.75);\n    }\n    return inside;\n}\n\nstatic void ui_label_character(ui_view_t* v, const char* utf8) {\n    rt_assert(v->type == ui_view_label);\n    if (v->state.hover && !ui_view.is_hidden(v)) {\n        char ch = utf8[0];\n        // Copy to clipboard works for hover over text\n        if ((ch == 3 || ch == 'c' || ch == 'C') && ui_app.ctrl) {\n            rt_clipboard.put_text(ui_view.string(v)); // 3 is ASCII for Ctrl+C\n        }\n    }\n}\n\nvoid ui_view_init_label(ui_view_t* v) {\n    rt_assert(v->type == ui_view_label);\n    v->paint         = ui_label_paint;\n    v->character     = ui_label_character;\n    v->context_menu  = ui_label_context_menu;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    v->text_align    = ui.align.left;\n}\n\nvoid ui_label_init_va(ui_label_t* v, fp32_t min_w_em,\n        const char* format, va_list va) {\n    ui_view.set_text(v, format, va);\n    v->min_w_em = min_w_em;\n    v->type = ui_view_label;\n    ui_view_init_label(v);\n}\n\nvoid ui_label_init(ui_label_t* v, fp32_t min_w_em, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_label_init_va(v, min_w_em, format, va);\n    va_end(va);\n}\n"
  },
  {
    "path": "src/ui/ui_mbx.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic void ui_mbx_button(ui_button_t* b) {\n    ui_mbx_t* m = (ui_mbx_t*)b->parent;\n    rt_assert(m->type == ui_view_mbx);\n    m->option = -1;\n    for (int32_t i = 0; i < rt_countof(m->button) && m->option < 0; i++) {\n        if (b == &m->button[i]) {\n            m->option = i;\n            if (m->callback != null) {\n                m->callback(&m->view);\n                // need to disarm button because message box about to close\n                b->state.pressed = false;\n                b->state.armed = false;\n            }\n        }\n    }\n    ui_app.show_toast(null, 0);\n}\n\nstatic void ui_mbx_measured(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    int32_t n = 0;\n    ui_view_for_each(v, c, { n++; });\n    n--; // number of buttons\n    const int32_t em_x = m->label.fm->em.w;\n    const int32_t em_y = m->label.fm->em.h;\n    const int32_t tw = m->label.w;\n    const int32_t th = m->label.h;\n    if (n > 0) {\n        int32_t bw = 0;\n        for (int32_t i = 0; i < n; i++) {\n            bw += m->button[i].w;\n        }\n        v->w = rt_max(tw, bw + em_x * 2);\n        v->h = th + m->button[0].h + em_y + em_y / 2;\n    } else {\n        v->h = th + em_y / 2;\n        v->w = tw;\n    }\n}\n\nstatic void ui_mbx_layout(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    int32_t n = 0;\n    ui_view_for_each(v, c, { n++; });\n    n--; // number of buttons\n    const int32_t em_y = m->label.fm->em.h;\n    m->label.x = v->x;\n    m->label.y = v->y + em_y * 2 / 3;\n    const int32_t tw = m->label.w;\n    const int32_t th = m->label.h;\n    if (n > 0) {\n        int32_t bw = 0;\n        for (int32_t i = 0; i < n; i++) {\n            bw += m->button[i].w;\n        }\n        // center text:\n        m->label.x = v->x + (v->w - tw) / 2;\n        // spacing between buttons:\n        int32_t sp = (v->w - bw) / (n + 1);\n        int32_t x = sp;\n        for (int32_t i = 0; i < n; i++) {\n            m->button[i].x = v->x + x;\n            m->button[i].y = v->y + th + em_y * 3 / 2;\n            x += m->button[i].w + sp;\n        }\n    }\n}\n\nvoid ui_view_init_mbx(ui_view_t* v) {\n    ui_mbx_t* m = (ui_mbx_t*)v;\n    v->measured = ui_mbx_measured;\n    v->layout = ui_mbx_layout;\n    m->fm = &ui_app.fm.prop.normal;\n    int32_t n = 0;\n    while (m->options[n] != null && n < rt_countof(m->button) - 1) {\n        m->button[n] = (ui_button_t)ui_button(\"\", 6.0, ui_mbx_button);\n        ui_view.set_text(&m->button[n], \"%s\", m->options[n]);\n        n++;\n    }\n    rt_swear(n <= rt_countof(m->button), \"inhumane: %d buttons is too many\", n);\n    if (n > rt_countof(m->button)) { n = rt_countof(m->button); }\n    m->label = (ui_label_t)ui_label(0, \"\");\n    ui_view.set_text(&m->label, \"%s\", ui_view.string(&m->view));\n    ui_view.add_last(&m->view, &m->label);\n    for (int32_t i = 0; i < n; i++) {\n        ui_view.add_last(&m->view, &m->button[i]);\n        m->button[i].fm = m->fm;\n    }\n    m->label.fm = m->fm;\n    ui_view.set_text(&m->view, \"\");\n    m->option = -1;\n    if (m->debug.id == null) { m->debug.id = \"#mbx\"; }\n}\n\nvoid ui_mbx_init(ui_mbx_t* m, const char* options[],\n        const char* format, ...) {\n    m->type = ui_view_mbx;\n    m->measured  = ui_mbx_measured;\n    m->layout    = ui_mbx_layout;\n    m->color_id  = ui_color_id_window;\n    m->options   = options;\n    m->focusable = true;\n    va_list va;\n    va_start(va, format);\n    ui_view.set_text_va(&m->view, format, va);\n    ui_label_init(&m->label, 0.0, ui_view.string(&m->view));\n    va_end(va);\n    ui_view_init_mbx(&m->view);\n}\n"
  },
  {
    "path": "src/ui/ui_midi.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"rt/rt_win32.h\"\n#include \"ui/ui.h\"\n#include <mmsystem.h>\n\n#pragma comment(lib, \"winmm\")\n\ntypedef struct ui_midi_s_ {\n    MCI_OPEN_PARMSA mop; // opaque\n    ui_app_message_handler_t handler;\n    char alias[32];\n    int64_t device_id;\n    uintptr_t window;\n    bool playing;\n} ui_midi_t_;\n\nrt_static_assertion(sizeof(ui_midi_t) >= sizeof(ui_midi_t_) + sizeof(void*));\nrt_static_assertion(MMSYSERR_NOERROR == 0);\n\nstatic void ui_midi_error(errno_t r, char* text, int32_t count) {\n    rt_fatal_win32err(mciGetErrorStringA(r, text, (UINT)count));\n}\n\nstatic void ui_midi_warn_if_error_(int r, const char* call, const char* func,\n        int line) {\n    if (r != 0) {\n        static char error[256];\n        ui_midi_error(r, error, rt_countof(error));\n        rt_println(\"%s:%d %s\", func, line, call);\n        rt_println(\"%d - MCIERR_BASE: %d %s\", r, r - MCIERR_BASE, error);\n    }\n}\n\n#define ui_midi_warn_if_error(r) do {                  \\\n    ui_midi_warn_if_error_(r, #r, __func__, __LINE__); \\\n} while (0)\n\n#define ui_midi_fatal_if_error(call) do {                                   \\\n    int _r_ = call; ui_midi_warn_if_error_(r, #call, __func__, __LINE__);   \\\n    rt_fatal_if_error(r);                                                   \\\n} while (0)\n\nstatic bool ui_midi_message_callback(ui_app_message_handler_t* h, int32_t m,\n                                     int64_t wp, int64_t lp, int64_t* rt) {\n    if (m == MM_MCINOTIFY) {\n        #ifdef UI_MIDI_DEBUG\n            rt_println(\"device_id: %lld\", lp);\n            if (wp & MCI_NOTIFY_SUCCESSFUL) { rt_println(\"SUCCESSFUL\"); }\n            if (wp & MCI_NOTIFY_SUPERSEDED) { rt_println(\"SUPERSEDED\"); }\n            if (wp & MCI_NOTIFY_ABORTED)    { rt_println(\"ABORTED\");    }\n            if (wp & MCI_NOTIFY_FAILURE)    { rt_println(\"FAILURE\");    }\n        #endif\n        ui_midi_t* midi = (ui_midi_t*)h->that;\n        ui_midi_t_* mi  = (ui_midi_t_*)midi;\n        if (mi->device_id == lp) {\n            if (midi->notify != null) {\n                *rt = midi->notify(midi, wp);\n            } else {\n                *rt = 0;\n            }\n            return true;\n        }\n    }\n    return false;\n}\n\nstatic void ui_midi_remove_handler(ui_midi_t* m) {\n    ui_midi_t_* mi  = (ui_midi_t_*)m;\n    ui_app_message_handler_t* h = ui_app.handlers;\n    if (h == &mi->handler) {\n        ui_app.handlers = h->next;\n    } else {\n        while (h->next != null && h->next != &mi->handler) {\n            h = h->next;\n        }\n        rt_swear(h->next == &mi->handler);\n        if (h->next == &mi->handler) {\n            h->next = h->next->next;\n        }\n    }\n    mi->handler.callback = null;\n    mi->handler.that = null;\n    mi->handler.next = null;\n}\n\nstatic errno_t ui_midi_open(ui_midi_t* m, const char* filename) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    mi->handler.that = mi;\n    mi->handler.next = ui_app.handlers;\n    ui_app.handlers = &mi->handler;\n    mi->window = (uintptr_t)ui_app.window;\n    mi->playing = false;\n    mi->mop.dwCallback = mi->window;\n    mi->mop.wDeviceID = (WORD)-1;\n    mi->mop.lpstrDeviceType = (const char*)MCI_DEVTYPE_SEQUENCER;\n    mi->mop.lpstrElementName = filename;\n    mi->mop.lpstrAlias = mi->alias;\n    rt_str_printf(mi->alias, \"%p\", m);\n    const DWORD_PTR flags = MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |\n                            MCI_OPEN_ELEMENT | MCI_OPEN_ALIAS;\n    errno_t r = mciSendCommandA(0, MCI_OPEN, flags, (uintptr_t)&mi->mop);\n    ui_midi_warn_if_error(r);\n    rt_assert(mi->mop.wDeviceID != -1);\n    mi->handler.callback = ui_midi_message_callback,\n    mi->device_id = mi->mop.wDeviceID;\n    if (r != 0) {\n        ui_midi_remove_handler(m);\n        memset(&mi->mop, 0x00, sizeof(mi->mop));\n        mi->window = 0;\n    }\n    return r;\n}\n\nstatic errno_t ui_midi_play(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    rt_swear(ui_midi.is_open(m));\n    MCI_PLAY_PARMS  pp = { .dwCallback = (uintptr_t)mi->window };\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_PLAY, MCI_NOTIFY, (uintptr_t)&pp);\n    ui_midi_warn_if_error(r);\n    if (r == 0) {\n        mi->playing = true;\n    }\n    return r;\n}\n\nstatic errno_t ui_midi_rewind(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    MCI_SEEK_PARMS p = { .dwCallback = (uintptr_t)mi->window, .dwTo = 0 };\n    const DWORD f = MCI_WAIT|MCI_SEEK_TO_START;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_SEEK, f, (DWORD_PTR)&p);\n    ui_midi_warn_if_error(r);\n    return r;\n}\n\nstatic errno_t ui_midi_get_volume(ui_midi_t* m, fp64_t* volume) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    DWORD v = 0;\n    errno_t r = midiOutGetVolume((HMIDIOUT)0, &v);\n    ui_midi_warn_if_error(r);\n    *volume = (fp64_t)v / (fp64_t)0xFFFFFFFFU;\n    return 0;\n}\n\nstatic errno_t ui_midi_set_volume(ui_midi_t* m, fp64_t volume) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    DWORD v = (DWORD)(volume * (fp64_t)0xFFFFFFFFU);\n    const UINT n = midiOutGetNumDevs();\n    // Handle to a MIDI Output Device\n    HMIDIOUT h = (HMIDIOUT)(uintptr_t)(n - 1);\n    errno_t r = n == 0 ? MCIERR_DEVICE_NOT_INSTALLED : midiOutSetVolume(h, v);\n    ui_midi_warn_if_error(r);\n    rt_fatal_if_error(r);\n    return r;\n}\n\nstatic errno_t ui_midi_stop(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && ui_midi.is_playing(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_STOP, 0, 0);\n    ui_midi_warn_if_error(r);\n    if (r == 0) { mi->playing = false; }\n    return r;\n}\n\nstatic void ui_midi_close(ui_midi_t* m) {\n    rt_swear(rt_thread.id() == ui_app.tid);\n    rt_swear(ui_midi.is_open(m) && !ui_midi.is_playing(m));\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    errno_t r = mciSendCommandA(mi->mop.wDeviceID, MCI_CLOSE, MCI_WAIT, 0);\n    ui_midi_warn_if_error(r);\n    r = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0);\n    ui_midi_warn_if_error(r);\n    rt_fatal_if_error(r, \"sound card is unplugged on the fly?\");\n    memset(&mi->mop, 0x00, sizeof(mi->mop));\n    mi->window = 0;\n    ui_midi_remove_handler(m);\n}\n\nstatic bool ui_midi_is_open(ui_midi_t* m) {\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    return mi->window != 0;\n}\n\nstatic bool ui_midi_is_playing(ui_midi_t* m) {\n    ui_midi_t_* mi = (ui_midi_t_*)m;\n    return mi->playing;\n}\n\nui_midi_if ui_midi = {\n    .success    = MCI_NOTIFY_SUCCESSFUL,\n    .failure    = MCI_NOTIFY_FAILURE,\n    .aborted    = MCI_NOTIFY_ABORTED,\n    .superseded = MCI_NOTIFY_SUPERSEDED,\n    .error      = ui_midi_error,\n    .open       = ui_midi_open,\n    .play       = ui_midi_play,\n    .rewind     = ui_midi_rewind,\n    .get_volume = ui_midi_get_volume,\n    .set_volume = ui_midi_set_volume,\n    .stop       = ui_midi_stop,\n    .is_open    = ui_midi_is_open,\n    .is_playing = ui_midi_is_playing,\n    .close      = ui_midi_close\n};"
  },
  {
    "path": "src/ui/ui_slider.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic void ui_slider_invalidate(const ui_slider_t* s) {\n    const ui_view_t* v = &s->view;\n    ui_view.invalidate(v, null);\n    if (!s->dec.state.hidden) { ui_view.invalidate(&s->dec, null); }\n    if (!s->inc.state.hidden) { ui_view.invalidate(&s->dec, null); }\n}\n\nstatic int32_t ui_slider_width(const ui_slider_t* s) {\n    const ui_ltrb_t i = ui_view.margins(&s->view, &s->insets);\n    int32_t w = s->w - i.left - i.right;\n    if (!s->dec.state.hidden) {\n        const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n        const ui_ltrb_t inc_p = ui_view.margins(&s->inc, &s->inc.padding);\n        w -= s->dec.w + s->inc.w + dec_p.right + inc_p.left;\n    }\n    return w;\n}\n\nstatic ui_wh_t measure_text(const ui_fm_t* fm, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    const ui_gdi_ta_t ta = { .fm = fm, .color = ui_colors.white, .measure = true };\n    ui_wh_t wh = ui_gdi.text_va(&ta, 0, 0, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic ui_wh_t ui_slider_measure_text(ui_slider_t* s) {\n    char formatted[rt_countof(s->p.text)];\n    const ui_fm_t* fm = s->fm;\n    const char* text = ui_view.string(&s->view);\n    const ui_ltrb_t i = ui_view.margins(&s->view, &s->insets);\n    ui_wh_t wh = s->fm->em;\n    if (s->debug.trace.mt) {\n        const ui_ltrb_t p = ui_view.margins(&s->view, &s->padding);\n        rt_println(\">%dx%d em: %dx%d min: %.1fx%.1f \"\n                \"i: %d %d %d %d p: %d %d %d %d \\\"%.*s\\\"\",\n            s->w, s->h, fm->em.w, fm->em.h, s->min_w_em, s->min_h_em,\n            i.left, i.top, i.right, i.bottom,\n            p.left, p.top, p.right, p.bottom,\n            rt_min(64, strlen(text)), text);\n        const ui_margins_t in = s->insets;\n        const ui_margins_t pd = s->padding;\n        rt_println(\" i: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\"\n                \" p: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\",\n            in.left, in.top, in.right, in.bottom,\n            in.left + in.right, in.top + in.bottom,\n            pd.left, pd.top, pd.right, pd.bottom,\n            pd.left + pd.right, pd.top + pd.bottom);\n    }\n    if (s->format != null) {\n        s->format(&s->view);\n        rt_str_printf(formatted, \"%s\", text);\n        wh = measure_text(s->fm, \"%s\", formatted);\n        // TODO: format string 0x08X?\n    } else if (text != null && (strstr(text, \"%d\") != null ||\n                                strstr(text, \"%u\") != null)) {\n        ui_wh_t mt_min = measure_text(s->fm, text, s->value_min);\n        ui_wh_t mt_max = measure_text(s->fm, text, s->value_max);\n        ui_wh_t mt_val = measure_text(s->fm, text, s->value);\n        wh.h = rt_max(mt_val.h, rt_max(mt_min.h, mt_max.h));\n        wh.w = rt_max(mt_val.w, rt_max(mt_min.w, mt_max.w));\n    } else if (text != null && text[0] != 0) {\n        wh = measure_text(s->fm, \"%s\", text);\n    }\n    if (s->debug.trace.mt) {\n        rt_println(\" mt: %dx%d\", wh.w, wh.h);\n    }\n    return wh;\n}\n\nstatic void ui_slider_measure(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // slider cannot be smaller than 2*em\n    const fp32_t min_w_em = rt_max(2.0f, v->min_w_em);\n    v->w = (int32_t)((fp64_t)fm->em.w * (fp64_t)   min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    // dec and inc have same font metrics as a slider:\n    s->dec.fm = fm;\n    s->inc.fm = fm;\n    rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"not the same\");\n    ui_view.measure_control(v);\n//  s->text.mt = ui_slider_measure_text(s);\n    if (s->dec.state.hidden) {\n        v->w = rt_max(v->w, i.left + s->wh.w + i.right);\n    } else {\n        ui_view.measure(&s->dec); // remeasure with inherited metrics\n        ui_view.measure(&s->inc);\n        const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n        const ui_ltrb_t inc_p = ui_view.margins(&s->inc, &s->inc.padding);\n        v->w = rt_max(v->w, s->dec.w + dec_p.right + s->wh.w + inc_p.left + s->inc.w);\n    }\n    v->h = rt_max(v->h, i.top + fm->em.h + i.bottom);\n    if (s->debug.trace.mt) {\n        rt_println(\"<%dx%d\", s->w, s->h);\n    }\n}\n\nstatic void ui_slider_layout(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    // disregard inc/dec .state.hidden bit for layout:\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    s->dec.x = v->x + i.left;\n    s->dec.y = v->y;\n    s->inc.x = v->x + v->w - i.right - s->inc.w;\n    s->inc.y = v->y;\n}\n\nstatic void ui_slider_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n    // dec button is sticking to the left into slider padding\n    const int32_t dec_w = s->dec.w + dec_p.right;\n    rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"hidden or not together\");\n    const int32_t dx = s->dec.state.hidden ? 0 : dec_w;\n    const int32_t x = v->x + dx + i.left;\n    const int32_t w = ui_slider_width(s);\n    // draw background:\n    fp32_t d = ui_theme.is_app_dark() ? 0.50f : 0.25f;\n    ui_color_t d0 = ui_colors.darken(v->background, d);\n    d /= 4;\n    ui_color_t d1 = ui_colors.darken(v->background, d);\n    ui_gdi.gradient(x, v->y, w, v->h, d1, d0, true);\n    // draw value:\n    ui_color_t c = ui_theme.is_app_dark() ?\n        ui_colors.darken(ui_colors.green, 1.0f / 128.0f) :\n        ui_colors.jungle_green;\n    d1 = c;\n    d0 = ui_colors.darken(c, 1.0f / 64.0f);\n    const fp64_t range = (fp64_t)s->value_max - (fp64_t)s->value_min;\n    rt_assert(range > 0, \"range: %.6f\", range);\n    const fp64_t  vw = (fp64_t)w * (s->value - s->value_min) / range;\n    const int32_t wi = (int32_t)(vw + 0.5);\n    ui_gdi.gradient(x, v->y, wi, v->h, d1, d0, true);\n    if (!v->flat) {\n        ui_color_t color = v->state.hover ?\n            ui_colors.get_color(ui_color_id_hot_tracking) :\n            ui_colors.get_color(ui_color_id_gray_text);\n        if (ui_view.is_disabled(v)) { color = ui_color_rgb(30, 30, 30); } // TODO: hardcoded\n        ui_gdi.frame(x, v->y, w, v->h, color);\n    }\n    // text:\n    const char* text = ui_view.string(v);\n    char formatted[rt_countof(v->p.text)];\n    if (s->format != null) {\n        s->format(v);\n        s->p.strid = 0; // nls again\n        text = ui_view.string(v);\n    } else if (text != null &&\n        (strstr(text, \"%d\") != null || strstr(text, \"%u\") != null)) {\n        rt_str.format(formatted, rt_countof(formatted), text, s->value);\n        s->p.strid = 0; // nls again\n        text = rt_nls.str(formatted);\n    }\n    // because current value was formatted into `text` need to\n    // remeasure and align text again:\n    ui_view.text_measure(v, text, &v->text);\n    ui_view.text_align(v, &v->text);\n    const ui_color_t text_color = !v->state.hover ? v->color :\n            (ui_theme.is_app_dark() ? ui_colors.white : ui_colors.black);\n    const ui_gdi_ta_t ta = { .fm = fm, .color = text_color };\n    ui_gdi.text(&ta, v->x + v->text.xy.x, v->y + v->text.xy.y, \"%s\", text);\n}\n\nstatic bool ui_slider_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        if (pressed) {\n            ui_slider_t* s = (ui_slider_t*)v;\n            const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n            const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n            const int32_t dec_w = s->dec.w + dec_p.right;\n            rt_assert(s->dec.state.hidden == s->inc.state.hidden, \"hidden or not together\");\n            const int32_t sw = ui_slider_width(s); // slider width\n            const int32_t dx = s->dec.state.hidden ? 0 : dec_w + dec_p.right;\n            const int32_t vx = v->x + i.left + dx;\n            const int32_t x = ui_app.mouse.x - vx;\n            const int32_t y = ui_app.mouse.y - (v->y + i.top);\n            if (0 <= x && x < sw && 0 <= y && y < v->h) {\n                const fp64_t range = (fp64_t)s->value_max - (fp64_t)s->value_min;\n                fp64_t val = (fp64_t)x * range / (fp64_t)(sw - 1);\n                int32_t vw = (int32_t)(val + s->value_min + 0.5);\n                s->value = rt_min(rt_max(vw, s->value_min), s->value_max);\n                if (s->callback != null) { s->callback(&s->view); }\n                ui_slider_invalidate(s);\n            }\n        }\n    }\n    return pressed && inside; // swallow inside clicks\n}\n\nstatic void ui_slider_mouse_move(ui_view_t* v) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (inside) {\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        ui_slider_t* s = (ui_slider_t*)v;\n        bool drag = ui_app.mouse_left || ui_app.mouse_right;\n        if (drag) {\n            const ui_ltrb_t dec_p = ui_view.margins(&s->dec, &s->dec.padding);\n            const int32_t dec_w = s->dec.w + dec_p.right;\n            rt_assert(s->dec.state.hidden == s->inc.state.hidden,\n                      \".dec .inc must be .hidden in sync\");\n            const int32_t sw = ui_slider_width(s); // slider width\n            const int32_t dx = s->dec.state.hidden ? 0 : dec_w + dec_p.right;\n            const int32_t vx = v->x + i.left + dx;\n            const int32_t x = ui_app.mouse.x - vx;\n            const int32_t y = ui_app.mouse.y - (v->y + i.top);\n            if (0 <= x && x < sw && 0 <= y && y < v->h) {\n                const fp64_t fmax = (fp64_t)s->value_max;\n                const fp64_t fmin = (fp64_t)s->value_min;\n                const fp64_t range = fmax - fmin;\n                fp64_t val = (fp64_t)x * range / (fp64_t)(sw - 1);\n                int32_t vw = (int32_t)(val + s->value_min + 0.5);\n                s->value = rt_min(rt_max(vw, s->value_min), s->value_max);\n                if (s->callback != null) { s->callback(&s->view); }\n                ui_slider_invalidate(s);\n            }\n        }\n    }\n}\n\nstatic void ui_slider_inc_dec_value(ui_slider_t* s, int32_t sign, int32_t mul) {\n    if (!ui_view.is_hidden(&s->view) && !ui_view.is_disabled(&s->view)) {\n        // full 0x80000000..0x7FFFFFFF (-2147483648..2147483647) range\n        int32_t v = s->value;\n        if (v > s->value_min && sign < 0) {\n            mul = rt_min(v - s->value_min, mul);\n            v += mul * sign;\n        } else if (v < s->value_max && sign > 0) {\n            mul = rt_min(s->value_max - v, mul);\n            v += mul * sign;\n        }\n        if (s->value != v) {\n            s->value = v;\n            if (s->callback != null) { s->callback(&s->view); }\n            ui_slider_invalidate(s);\n        }\n    }\n}\n\nstatic void ui_slider_inc_dec(ui_button_t* b) {\n    ui_slider_t* s = (ui_slider_t*)b->parent;\n    if (!ui_view.is_hidden(&s->view) && !ui_view.is_disabled(&s->view)) {\n        int32_t sign = b == &s->inc ? +1 : -1;\n        int32_t mul = ui_app.shift && ui_app.ctrl ? 1000 :\n            ui_app.shift ? 100 : ui_app.ctrl ? 10 : 1;\n        ui_slider_inc_dec_value(s, sign, mul);\n    }\n}\n\nstatic void ui_slider_every_100ms(ui_view_t* v) { // 100ms\n    rt_assert(v->type == ui_view_slider);\n    ui_slider_t* s = (ui_slider_t*)v;\n    if (ui_view.is_hidden(v) || ui_view.is_disabled(v)) {\n        s->time = 0;\n    } else if (!s->dec.state.armed && !s->inc.state.armed) {\n        s->time = 0;\n    } else {\n        if (s->time == 0) {\n            s->time = ui_app.now;\n        } else if (ui_app.now - s->time > 1.0) {\n            const int32_t sign = s->dec.state.armed ? -1 : +1;\n            const int32_t sec = (int32_t)(ui_app.now - s->time + 0.5);\n            int32_t initial = ui_app.shift && ui_app.ctrl ? 1000 :\n                ui_app.shift ? 100 : ui_app.ctrl ? 10 : 1;\n            int32_t mul = sec >= 1 ? initial << (sec - 1) : initial;\n            const int64_t range = (int64_t)s->value_max - (int64_t)s->value_min;\n            if (mul > range / 8) { mul = (int32_t)(range / 8); }\n            ui_slider_inc_dec_value(s, sign, rt_max(mul, 1));\n        }\n    }\n}\n\nvoid ui_view_init_slider(ui_view_t* v) {\n    rt_assert(v->type == ui_view_slider);\n    v->measure       = ui_slider_measure;\n    v->layout        = ui_slider_layout;\n    v->paint         = ui_slider_paint;\n    v->tap           = ui_slider_tap;\n    v->mouse_move    = ui_slider_mouse_move;\n    v->every_100ms   = ui_slider_every_100ms;\n    v->color_id      = ui_color_id_window_text;\n    v->background_id = ui_color_id_button_face;\n    ui_slider_t* s = (ui_slider_t*)v;\n    static const char* accel =\n        \" Hold key while clicking\\n\"\n        \" Ctrl: x 10 Shift: x 100 \\n\"\n        \" Ctrl+Shift: x 1000 \\n for step multiplier.\";\n    s->dec = (ui_button_t)ui_button(rt_glyph_fullwidth_hyphen_minus, 0, // rt_glyph_heavy_minus_sign\n                                    ui_slider_inc_dec);\n    s->dec.fm = v->fm;\n    rt_str_printf(s->dec.hint, \"%s\", accel);\n    s->inc = (ui_button_t)ui_button(rt_glyph_fullwidth_plus_sign, 0, // rt_glyph_heavy_plus_sign\n                                    ui_slider_inc_dec);\n    s->inc.fm = v->fm;\n    ui_view.add(&s->view, &s->dec, &s->inc, null);\n    // single glyph buttons less insets look better:\n    ui_view_for_each(&s->view, it, {\n        it->insets.left   = 0.125f;\n        it->insets.right  = 0.125f;\n    });\n    // inherit initial padding and insets from buttons.\n    // caller may change those later and it should be accounted to\n    // in measure() and layout()\n    v->insets  = s->dec.insets;\n    v->padding = s->dec.padding;\n    s->dec.padding.right = 0;\n    s->dec.padding.left  = 0;\n    s->inc.padding.left  = 0;\n    s->inc.padding.right = 0;\n    s->dec.flat = true;\n    s->inc.flat = true;\n    s->dec.min_h_em = 1.0f + ui_view_i_tb * 2;\n    s->dec.min_w_em = 1.0f + ui_view_i_tb * 2;\n    s->inc.min_h_em = 1.0f + ui_view_i_tb * 2;\n    s->inc.min_w_em = 1.0f + ui_view_i_tb * 2;\n    rt_str_printf(s->inc.hint, \"%s\", accel);\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    if (v->debug.id == null) { v->debug.id = \"#slider\"; }\n}\n\nvoid ui_slider_init(ui_slider_t* s, const char* label, fp32_t min_w_em,\n        int32_t value_min, int32_t value_max,\n        void (*callback)(ui_view_t* r)) {\n    static_assert(offsetof(ui_slider_t, view) == 0, \"offsetof(.view)\");\n    if (min_w_em < 6.0) { rt_println(\"6.0 em minimum\"); }\n    s->type = ui_view_slider;\n    ui_view.set_text(&s->view, \"%s\", label);\n    s->callback = callback;\n    s->min_w_em = rt_max(6.0f, min_w_em);\n    s->value_min = value_min;\n    s->value_max = value_max;\n    s->value = value_min;\n    ui_view_init_slider(&s->view);\n}\n"
  },
  {
    "path": "src/ui/ui_theme.c",
    "content": "/* Copyright (c) Dmitry \"Leo\" Kuznetsov 2021-24 see LICENSE for details */\n#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n#include \"ui/rt_win32.h\"\n\nstatic int32_t ui_theme_dark = -1; // -1 unknown\n\nstatic errno_t ui_theme_reg_get_uint32(HKEY root, const char* path,\n        const char* key, DWORD *v) {\n    *v = 0;\n    DWORD type = REG_DWORD;\n    DWORD light_theme = 0;\n    DWORD bytes = sizeof(light_theme);\n    errno_t r = RegGetValueA(root, path, key, RRF_RT_DWORD, &type, v, &bytes);\n    if (r != 0) {\n        rt_println(\"RegGetValueA(%s\\\\%s) failed %s\", path, key, rt_strerr(r));\n    }\n    return r;\n}\n\n#pragma push_macro(\"ux_theme_reg_cv\")\n#pragma push_macro(\"ux_theme_reg_default_colors\")\n\n#define ux_theme_reg_cv \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\\"\n#define ux_theme_reg_default_colors ux_theme_reg_cv \"Themes\\\\DefaultColors\\\\\"\n\nstatic bool ui_theme_use_light_theme(const char* key) {\n    if ((!ui_app.dark_mode && !ui_app.light_mode) ||\n        ( ui_app.dark_mode &&  ui_app.light_mode)) {\n        const char* personalize  = ux_theme_reg_cv \"Themes\\\\Personalize\";\n        DWORD light_theme = 0;\n        ui_theme_reg_get_uint32(HKEY_CURRENT_USER, personalize, key, &light_theme);\n        return light_theme != 0;\n    } else if (ui_app.light_mode) {\n        return true;\n    } else {\n        rt_assert(ui_app.dark_mode);\n        return false;\n    }\n}\n\n#pragma pop_macro(\"ux_theme_reg_cv\")\n#pragma pop_macro(\"ux_theme_reg_default_colors\")\n\nstatic HMODULE ui_theme_uxtheme(void) {\n    static HMODULE uxtheme;\n    if (uxtheme == null) {\n        uxtheme = GetModuleHandleA(\"uxtheme.dll\");\n        if (uxtheme == null) {\n            uxtheme = LoadLibraryA(\"uxtheme.dll\");\n        }\n    }\n    rt_not_null(uxtheme);\n    return uxtheme;\n}\n\nstatic void* ui_theme_uxtheme_func(uint16_t ordinal) {\n    HMODULE uxtheme = ui_theme_uxtheme();\n    void* proc = (void*)GetProcAddress(uxtheme, MAKEINTRESOURCEA(ordinal));\n    rt_not_null(proc);\n    return proc;\n}\n\nstatic void ui_theme_set_preferred_app_mode(int32_t mode) {\n    typedef BOOL (__stdcall *SetPreferredAppMode_t)(int32_t mode);\n    SetPreferredAppMode_t SetPreferredAppMode = (SetPreferredAppMode_t)\n            (SetPreferredAppMode_t)ui_theme_uxtheme_func(135);\n    errno_t r = rt_b2e(SetPreferredAppMode(mode));\n    // On Win11: 10.0.22631\n    // SetPreferredAppMode(true) failed 0x0000047E(1150) ERROR_OLD_WIN_VERSION\n    // \"The specified program requires a newer version of Windows.\"\n    if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n        rt_println(\"SetPreferredAppMode(AllowDark) failed %s\", rt_strerr(r));\n    }\n}\n\n// https://stackoverflow.com/questions/75835069/dark-system-contextmenu-in-window\n\nstatic void ui_theme_flush_menu_themes(void) {\n    typedef BOOL (__stdcall *FlushMenuThemes_t)(void);\n    FlushMenuThemes_t FlushMenuThemes = (FlushMenuThemes_t)\n            (FlushMenuThemes_t)ui_theme_uxtheme_func(136);\n    errno_t r = rt_b2e(FlushMenuThemes());\n    // FlushMenuThemes() works but returns ERROR_OLD_WIN_VERSION\n    // on newest Windows 11 but it is not documented thus no complains.\n    if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n        rt_println(\"FlushMenuThemes(AllowDark) failed %s\", rt_strerr(r));\n    }\n}\n\nstatic void ui_theme_allow_dark_mode_for_app(bool allow) {\n    // https://github.com/rizonesoft/Notepad3/tree/96a48bd829a3f3192bbc93cd6944cafb3228b96d/src/DarkMode\n    typedef BOOL (__stdcall *AllowDarkModeForApp_t)(bool allow);\n    AllowDarkModeForApp_t AllowDarkModeForApp =\n            (AllowDarkModeForApp_t)ui_theme_uxtheme_func(132);\n    if (AllowDarkModeForApp != null) {\n        errno_t r = rt_b2e(AllowDarkModeForApp(allow));\n        if (r != 0 && r != ERROR_PROC_NOT_FOUND) {\n            rt_println(\"AllowDarkModeForApp(true) failed %s\", rt_strerr(r));\n        }\n    }\n}\n\nstatic void ui_theme_allow_dark_mode_for_window(bool allow) {\n    typedef BOOL (__stdcall *AllowDarkModeForWindow_t)(HWND hWnd, bool allow);\n    AllowDarkModeForWindow_t AllowDarkModeForWindow =\n        (AllowDarkModeForWindow_t)ui_theme_uxtheme_func(133);\n    if (AllowDarkModeForWindow != null) {\n        int r = rt_b2e(AllowDarkModeForWindow((HWND)ui_app.window, allow));\n        // On Win11: 10.0.22631\n        // AllowDarkModeForWindow(true) failed 0x0000047E(1150) ERROR_OLD_WIN_VERSION\n        // \"The specified program requires a newer version of Windows.\"\n        if (r != 0 && r != ERROR_PROC_NOT_FOUND && r != ERROR_OLD_WIN_VERSION) {\n            rt_println(\"AllowDarkModeForWindow(true) failed %s\", rt_strerr(r));\n        }\n    }\n}\n\nstatic bool ui_theme_are_apps_dark(void) {\n    return !ui_theme_use_light_theme(\"AppsUseLightTheme\");\n}\n\nstatic bool ui_theme_is_system_dark(void) {\n    return !ui_theme_use_light_theme(\"SystemUsesLightTheme\");\n}\n\nstatic bool ui_theme_is_app_dark(void) {\n    if (ui_theme_dark < 0) { ui_theme_dark = ui_theme.are_apps_dark(); }\n    return ui_theme_dark;\n}\n\nstatic void ui_theme_refresh(void) {\n    rt_swear(ui_app.window != null);\n    ui_theme_dark = -1;\n    BOOL dark_mode = ui_theme_is_app_dark(); // must be 32-bit \"BOOL\"\n    static const DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20;\n    /* 20 == DWMWA_USE_IMMERSIVE_DARK_MODE in Windows 11 SDK.\n       This value was undocumented for Windows 10 versions 2004\n       and later, supported for Windows 11 Build 22000 and later. */\n    errno_t r = DwmSetWindowAttribute((HWND)ui_app.window,\n        DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_mode, sizeof(dark_mode));\n    if (r != 0) {\n        rt_println(\"DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) \"\n                \"failed %s\", rt_strerr(r));\n    }\n    ui_theme.allow_dark_mode_for_app(dark_mode);\n    ui_theme.allow_dark_mode_for_window(dark_mode);\n    ui_theme.set_preferred_app_mode(dark_mode ?\n        ui_theme_app_mode_force_dark : ui_theme_app_mode_force_light);\n    ui_theme.flush_menu_themes();\n    ui_app.request_layout();\n}\n\nui_theme_if ui_theme = {\n    .is_app_dark                  = ui_theme_is_app_dark,\n    .is_system_dark               = ui_theme_is_system_dark,\n    .are_apps_dark                = ui_theme_are_apps_dark,\n    .set_preferred_app_mode       = ui_theme_set_preferred_app_mode,\n    .flush_menu_themes            = ui_theme_flush_menu_themes,\n    .allow_dark_mode_for_app      = ui_theme_allow_dark_mode_for_app,\n    .allow_dark_mode_for_window   = ui_theme_allow_dark_mode_for_window,\n    .refresh                      = ui_theme_refresh,\n};\n\n\n"
  },
  {
    "path": "src/ui/ui_toggle.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic void ui_toggle_paint_on_off(ui_view_t* v) {\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    int32_t x = v->x;\n    int32_t y = v->y + i.top;\n    ui_color_t c = ui_colors.darken(v->background,\n        !ui_theme.is_app_dark() ? 0.125f : 0.5f);\n    ui_color_t b = v->state.pressed ? ui_colors.tone_green : c;\n    const int32_t a = v->fm->ascent;\n    const int32_t d = v->fm->descent;\n    const int32_t w = v->fm->em.w;\n    int32_t r = ((a + d + 1) / 2) | 0x1; // radius must be odd\n    int32_t h = r * 2 + 1;\n    y += (v->h - i.top - i.bottom - h + 1) / 2;\n    y += r + 1; // because radius is odd\n    x += r;\n    ui_color_t border = ui_theme.is_app_dark() ?\n        ui_colors.darken(v->color, 0.5) :\n        ui_colors.lighten(v->color, 0.5);\n    if (v->state.hover) {\n        border = ui_colors.get_color(ui_color_id_hot_tracking);\n    }\n    ui_gdi.circle(x, y, r, border, b);\n    ui_gdi.circle(x + w - r, y, r, border, b);\n    ui_gdi.fill(x, y - r, w - r + 1, h, b);\n    ui_gdi.line(x, y - r, x + w - r + 1, y - r, border);\n    ui_gdi.line(x, y + r, x + w - r + 1, y + r, border);\n    int32_t x1 = v->state.pressed ? x + w - r : x;\n    // circle is too bold in control color - water it down\n    ui_color_t fill = ui_theme.is_app_dark() ?\n        ui_colors.darken(v->color, 0.5f) : ui_colors.lighten(v->color, 0.5f);\n    border = ui_theme.is_app_dark() ?\n        ui_colors.darken(fill, 0.0625f) : ui_colors.lighten(fill, 0.0625f);\n    ui_gdi.circle(x1, y, r - 2, border, fill);\n}\n\nstatic const char* ui_toggle_on_off_label(ui_view_t* v,\n        char* label, int32_t count)  {\n    rt_str.format(label, count, \"%s\", ui_view.string(v));\n    char* s = strstr(label, \"___\");\n    if (s != null) {\n        memcpy(s, v->state.pressed ? \"On \" : \"Off\", 3);\n    }\n    return rt_nls.str(label);\n}\n\nstatic void ui_toggle_measure(ui_view_t* v) {\n    if (v->min_w_em < 3.0f) {\n        rt_println(\"3.0f em minimum width\");\n        v->min_w_em = 4.0f;\n    }\n    ui_view.measure_control(v);\n    rt_assert(v->type == ui_view_toggle);\n}\n\nstatic void ui_toggle_paint(ui_view_t* v) {\n    rt_assert(v->type == ui_view_toggle);\n    char txt[rt_countof(v->p.text)];\n    const char* label = ui_toggle_on_off_label(v, txt, rt_countof(txt));\n    const char* text = rt_nls.str(label);\n    ui_view.text_measure(v, text, &v->text);\n    ui_view.text_align(v, &v->text);\n    ui_toggle_paint_on_off(v);\n    const ui_color_t text_color = !v->state.hover ? v->color :\n            (ui_theme.is_app_dark() ? ui_colors.white : ui_colors.black);\n    const ui_gdi_ta_t ta = { .fm = v->fm, .color = text_color };\n    ui_gdi.text(&ta, v->x + v->text.xy.x, v->y + v->text.xy.y, \"%s\", text);\n}\n\nstatic void ui_toggle_flip(ui_toggle_t* t) {\n    ui_view.invalidate((ui_view_t*)t, null);\n    t->state.pressed = !t->state.pressed;\n    if (t->callback != null) { t->callback(t); }\n}\n\nstatic void ui_toggle_character(ui_view_t* v, const char* utf8) {\n    char ch = utf8[0];\n    if (ui_view.is_shortcut_key(v, ch)) {\n         ui_toggle_flip((ui_toggle_t*)v);\n    }\n}\n\nstatic bool ui_toggle_key_pressed(ui_view_t* v, int64_t key) {\n    const bool trigger = ui_app.alt && ui_view.is_shortcut_key(v, key);\n    if (trigger) { ui_toggle_flip((ui_toggle_t*)v); }\n    return trigger; // swallow if true\n}\n\nstatic bool ui_toggle_tap(ui_view_t* v, int32_t rt_unused(ix),\n        bool pressed) {\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    if (pressed && inside) { ui_toggle_flip((ui_toggle_t*)v); }\n    return pressed && inside;\n}\n\nvoid ui_view_init_toggle(ui_view_t* v) {\n    rt_assert(v->type == ui_view_toggle);\n    v->tap           = ui_toggle_tap;\n    v->paint         = ui_toggle_paint;\n    v->measure       = ui_toggle_measure;\n    v->character     = ui_toggle_character;\n    v->key_pressed   = ui_toggle_key_pressed;\n    v->color_id      = ui_color_id_button_text;\n    v->background_id = ui_color_id_button_face;\n    v->text_align    = ui.align.left;\n    if (v->debug.id == null) { v->debug.id = \"#toggle\"; }\n}\n\nvoid ui_toggle_init(ui_toggle_t* t, const char* label, fp32_t ems,\n       void (*callback)(ui_toggle_t* b)) {\n    ui_view.set_text(t, \"%s\", label);\n    t->min_w_em = ems;\n    t->callback = callback;\n    t->type = ui_view_toggle;\n    ui_view_init_toggle(t);\n}\n"
  },
  {
    "path": "src/ui/ui_view.c",
    "content": "#include \"rt/rt.h\"\n#include \"ui/ui.h\"\n\nstatic const fp64_t ui_view_hover_delay = 1.5; // seconds\n\n#pragma push_macro(\"ui_view_for_each\")\n\nstatic void ui_view_update_shortcut(ui_view_t* v);\n\n// adding and removing views is not expected to be frequent\n// actions by application code (human factor - UI design)\n// thus extra checks and verifications are there even in\n// release code because C is not type safety champion language.\n\nstatic inline void ui_view_check_type(ui_view_t* v) {\n    // little endian:\n    rt_static_assertion(('vwXX' & 0xFFFF0000U) == ('vwZZ' & 0xFFFF0000U));\n    rt_static_assertion((ui_view_stack & 0xFFFF0000U) == ('vwXX' & 0xFFFF0000U));\n    rt_swear(((uint32_t)v->type & 0xFFFF0000U) == ('vwXX'  & 0xFFFF0000U),\n          \"not a view: %4.4s 0x%08X (forgotten &static_view?)\",\n          &v->type, v->type);\n}\n\nstatic void ui_view_verify(ui_view_t* p) {\n    ui_view_check_type(p);\n    ui_view_for_each(p, c, {\n        ui_view_check_type(c);\n        ui_view_update_shortcut(c);\n        rt_swear(c->parent == p);\n        rt_swear(c == c->next->prev);\n        rt_swear(c == c->prev->next);\n    });\n}\n\nstatic ui_view_t* ui_view_add(ui_view_t* p, ...) {\n    va_list va;\n    va_start(va, p);\n    ui_view_t* c = va_arg(va, ui_view_t*);\n    while (c != null) {\n        rt_swear(c->parent == null && c->prev == null && c->next == null);\n        ui_view.add_last(p, c);\n        c = va_arg(va, ui_view_t*);\n    }\n    va_end(va);\n    ui_view_call_init(p);\n    ui_app.request_layout();\n    return p;\n}\n\nstatic void ui_view_add_first(ui_view_t* p, ui_view_t* c) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    c->parent = p;\n    if (p->child == null) {\n        c->prev = c;\n        c->next = c;\n    } else {\n        c->prev = p->child->prev;\n        c->next = p->child;\n        c->prev->next = c;\n        c->next->prev = c;\n    }\n    p->child = c;\n    ui_view_call_init(c);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_last(ui_view_t* p, ui_view_t* c) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    c->parent = p;\n    if (p->child == null) {\n        c->prev = c;\n        c->next = c;\n        p->child = c;\n    } else {\n        c->prev = p->child->prev;\n        c->next = p->child;\n        c->prev->next = c;\n        c->next->prev = c;\n    }\n    ui_view_call_init(c);\n    ui_view_verify(p);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_after(ui_view_t* c, ui_view_t* a) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    rt_not_null(a->parent);\n    c->parent = a->parent;\n    c->next = a->next;\n    c->prev = a;\n    a->next = c;\n    c->prev->next = c;\n    c->next->prev = c;\n    ui_view_call_init(c);\n    ui_view_verify(c->parent);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_add_before(ui_view_t* c, ui_view_t* b) {\n    rt_swear(c->parent == null && c->prev == null && c->next == null);\n    rt_not_null(b->parent);\n    c->parent = b->parent;\n    c->prev = b->prev;\n    c->next = b;\n    b->prev = c;\n    c->prev->next = c;\n    c->next->prev = c;\n    ui_view_call_init(c);\n    ui_view_verify(c->parent);\n    ui_app.request_layout();\n}\n\nstatic void ui_view_remove(ui_view_t* c) {\n    rt_not_null(c->parent);\n    rt_not_null(c->parent->child);\n    // if a view that has focus is removed from parent:\n    if (c == ui_app.focus) { ui_view.set_focus(null); }\n    if (c->prev == c) {\n        rt_swear(c->next == c);\n        c->parent->child = null;\n    } else {\n        c->prev->next = c->next;\n        c->next->prev = c->prev;\n        if (c->parent->child == c) {\n            c->parent->child = c->next;\n        }\n    }\n    c->prev = null;\n    c->next = null;\n    ui_view_verify(c->parent);\n    c->parent = null;\n    ui_app.request_layout();\n}\n\nstatic void ui_view_remove_all(ui_view_t* p) {\n    while (p->child != null) { ui_view.remove(p->child); }\n    ui_app.request_layout();\n}\n\nstatic void ui_view_disband(ui_view_t* p) {\n    // do not disband composite controls\n    if (p->type != ui_view_mbx && p->type != ui_view_slider) {\n        while (p->child != null) {\n            ui_view_disband(p->child);\n            ui_view.remove(p->child);\n        }\n    }\n    ui_app.request_layout();\n}\n\nstatic void ui_view_invalidate(const ui_view_t* v, const ui_rect_t* r) {\n    if (ui_view.is_hidden(v)) {\n        rt_println(\"hidden: %s\", ui_view_debug_id(v));\n    } else {\n        ui_rect_t rc = {0};\n        if (r != null) {\n            rc = (ui_rect_t){\n                .x = v->x + r->x,\n                .y = v->y + r->y,\n                .w = r->w,\n                .h = r->h\n            };\n        } else {\n            rc = (ui_rect_t){ v->x, v->y, v->w, v->h};\n            // expand view rectangle by padding\n            const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n            rc.x -= p.left;\n            rc.y -= p.top;\n            rc.w += p.left + p.right;\n            rc.h += p.top + p.bottom;\n        }\n        if (v->debug.trace.prc) {\n            rt_println(\"%d,%d %dx%d\", rc.x, rc.y, rc.w, rc.h);\n        }\n        ui_app.invalidate(&rc);\n    }\n}\n\nstatic const char* ui_view_string(ui_view_t* v) {\n    if (v->p.strid == 0) {\n        int32_t id = rt_nls.strid(v->p.text);\n        v->p.strid = id > 0 ? id : -1;\n    }\n    return v->p.strid < 0 ? v->p.text : // not localized\n        rt_nls.string(v->p.strid, v->p.text);\n}\n\nstatic ui_wh_t ui_view_text_metrics_va(int32_t x, int32_t y,\n        bool multiline, int32_t w, const ui_fm_t* fm,\n        const char* format, va_list va) {\n    const ui_gdi_ta_t ta = { .fm = fm, .color = ui_colors.transparent,\n                             .measure = true };\n    return multiline ?\n        ui_gdi.multiline_va(&ta, x, y, w, format, va) :\n        ui_gdi.text_va(&ta, x, y, format, va);\n}\n\nstatic ui_wh_t ui_view_text_metrics(int32_t x, int32_t y,\n        bool multiline, int32_t w, const ui_fm_t* fm,\n        const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_wh_t wh = ui_view_text_metrics_va(x, y, multiline, w, fm, format, va);\n    va_end(va);\n    return wh;\n}\n\nstatic void ui_view_text_measure(ui_view_t* v, const char* s,\n        ui_view_text_metrics_t* tm) {\n    const ui_fm_t* fm = v->fm;\n    tm->wh = (ui_wh_t){ .w = 0, .h = fm->height };\n    if (s[0] == 0) {\n        tm->multiline = false;\n    } else {\n        tm->multiline = strchr(s, '\\n') != null;\n        if (v->type == ui_view_label && tm->multiline) {\n            int32_t w = (int32_t)((fp64_t)v->min_w_em * (fp64_t)fm->em.w + 0.5);\n            tm->wh = ui_view.text_metrics(v->x, v->y, true,  w, fm, \"%s\", s);\n        } else {\n            tm->wh = ui_view.text_metrics(v->x, v->y, false, 0, fm, \"%s\", s);\n        }\n    }\n}\n\nstatic void ui_view_text_align(ui_view_t* v, ui_view_text_metrics_t* tm) {\n    tm->xy = (ui_point_t){ .x = -1, .y = -1 };\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    // i_wh the inside insets w x h:\n    const ui_wh_t i_wh = { .w = v->w - i.left - i.right,\n                           .h = v->h - i.top - i.bottom };\n    const int32_t h_align = v->text_align & ~(ui.align.top|ui.align.bottom);\n    const int32_t v_align = v->text_align & ~(ui.align.left|ui.align.right);\n    tm->xy.x = i.left + (i_wh.w - tm->wh.w + 1) / 2;\n    if (h_align & ui.align.left) {\n        tm->xy.x = i.left;\n    } else if (h_align & ui.align.right) {\n        tm->xy.x = i_wh.w - tm->wh.w - i.right;\n    }\n    // vertical centering is trickier.\n    // mt.h is height of all measured lines of text\n    tm->xy.y = i.top + (i_wh.h - tm->wh.h + 1) / 2;\n    if (v_align & ui.align.top) {\n        tm->xy.y = i.top;\n    } else if (v_align & ui.align.bottom) {\n        tm->xy.y = i_wh.h - tm->wh.h - i.bottom;\n    } else if (!tm->multiline) {\n#if 0 // TODO: doesn't look good or right:\n        // UI controls should have x-height line in the dead center\n        // of the control to be visually balanced.\n        // y offset of \"x-line\" of the glyph:\n        const ui_fm_t* fm = v->fm;\n        const int32_t y_of_x_line = fm->baseline - fm->x_height;\n        // `dy` offset of the center to x-line (middle of glyph cell)\n        const int32_t dy = tm->wh.h / 2 - y_of_x_line;\n        tm->xy.y += dy / 2;\n        if (v->debug.trace.mt) {\n            rt_println(\" x-line: %d mt.h: %d mt.h / 2 - x_line: %d\",\n                      y_of_x_line, tm->wh.h, dy);\n        }\n#endif\n    }\n}\n\nstatic void ui_view_measure_control(ui_view_t* v) {\n    v->p.strid = 0;\n    const char* s = ui_view.string(v);\n    const ui_fm_t* fm = v->fm;\n    const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n    v->w = (int32_t)((fp64_t)fm->em.w * (fp64_t)v->min_w_em + 0.5);\n    v->h = (int32_t)((fp64_t)fm->em.h * (fp64_t)v->min_h_em + 0.5);\n    if (v->debug.trace.mt) {\n        const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n        rt_println(\">%dx%d em: %dx%d min: %.3fx%.3f \"\n                \"i: %d %d %d %d p: %d %d %d %d %s \\\"%.*s\\\"\",\n            v->w, v->h, fm->em.w, fm->em.h, v->min_w_em, v->min_h_em,\n            i.left, i.top, i.right, i.bottom,\n            p.left, p.top, p.right, p.bottom,\n            ui_view_debug_id(v),\n            rt_min(64, strlen(s)), s);\n        const ui_margins_t in = v->insets;\n        const ui_margins_t pd = v->padding;\n        rt_println(\" i: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\"\n                \" p: %.3f %.3f %.3f %.3f l+r: %.3f t+b: %.3f\",\n            in.left, in.top, in.right, in.bottom,\n            in.left + in.right, in.top + in.bottom,\n            pd.left, pd.top, pd.right, pd.bottom,\n            pd.left + pd.right, pd.top + pd.bottom);\n    }\n    ui_view_text_measure(v, s, &v->text);\n    if (v->debug.trace.mt) {\n        rt_println(\" mt: %d %d\", v->text.wh.w, v->text.wh.h);\n    }\n    v->w = rt_max(v->w, i.left + v->text.wh.w + i.right);\n    v->h = rt_max(v->h, i.top  + v->text.wh.h + i.bottom);\n    ui_view_text_align(v, &v->text);\n    if (v->debug.trace.mt) {\n        rt_println(\"<%dx%d text_align x,y: %d,%d %s\",\n                v->w, v->h, v->text.xy.x, v->text.xy.y,\n                ui_view_debug_id(v));\n    }\n}\n\nstatic void ui_view_measure_children(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_for_each(v, c, { ui_view.measure(c); });\n    }\n}\n\nstatic void ui_view_measure(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_measure_children(v);\n        if (v->prepare != null) { v->prepare(v); }\n        if (v->measure != null && v->measure != ui_view_measure) {\n            v->measure(v);\n        } else {\n            ui_view.measure_control(v);\n        }\n        if (v->measured != null) { v->measured(v); }\n    }\n}\n\nstatic void ui_layout_view(ui_view_t* rt_unused(v)) {\n//  ui_ltrb_t i = ui_view.margins(v, &v->insets);\n//  ui_ltrb_t p = ui_view.margins(v, &v->padding);\n//  rt_println(\">%s %d,%d %dx%d p: %d %d %d %d  i: %d %d %d %d\",\n//               v->p.text, v->x, v->y, v->w, v->h,\n//               p.left, p.top, p.right, p.bottom,\n//               i.left, i.top, i.right, i.bottom);\n//  rt_println(\"<%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n}\n\nstatic void ui_view_layout_children(ui_view_t* v) {\n    if (!ui_view.is_hidden(v)) {\n        ui_view_for_each(v, c, { ui_view.layout(c); });\n    }\n}\n\nstatic void ui_view_layout(ui_view_t* v) {\n//  rt_println(\">%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n    if (!ui_view.is_hidden(v)) {\n        if (v->layout != null && v->layout != ui_view_layout) {\n            v->layout(v);\n        } else {\n            ui_layout_view(v);\n        }\n        if (v->composed != null) { v->composed(v); }\n        ui_view_layout_children(v);\n    }\n//  rt_println(\"<%s %d,%d %dx%d\", v->p.text, v->x, v->y, v->w, v->h);\n}\n\nstatic bool ui_view_inside(const ui_view_t* v, const ui_point_t* pt) {\n    const int32_t x = pt->x - v->x;\n    const int32_t y = pt->y - v->y;\n    return 0 <= x && x < v->w && 0 <= y && y < v->h;\n}\n\nstatic bool ui_view_is_parent_of(const ui_view_t* parent,\n        const ui_view_t* child) {\n    rt_swear(parent != null && child != null);\n    const ui_view_t* p = child->parent;\n    while (p != null) {\n        if (parent == p) { return true; }\n        p = p->parent;\n    }\n    return false;\n}\n\nstatic ui_ltrb_t ui_view_margins(const ui_view_t* v, const ui_margins_t* m) {\n    const fp64_t gw = (fp64_t)m->left + (fp64_t)m->right;\n    const fp64_t gh = (fp64_t)m->top  + (fp64_t)m->bottom;\n    const ui_wh_t* em = &v->fm->em;\n    const int32_t em_w = (int32_t)(em->w * gw + 0.5);\n    const int32_t em_h = (int32_t)(em->h * gh + 0.5);\n    const int32_t left = (int32_t)((fp64_t)em->w * (fp64_t)m->left + 0.5);\n    const int32_t top  = (int32_t)((fp64_t)em->h * (fp64_t)m->top  + 0.5);\n    return (ui_ltrb_t) {\n        .left   = left,         .top    = top,\n        .right  = em_w - left,  .bottom = em_h - top\n    };\n}\n\nstatic void ui_view_inbox(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* insets) {\n    rt_swear(r != null || insets != null);\n    rt_swear(v->max_w >= 0 && v->max_h >= 0);\n    const ui_ltrb_t i = ui_view_margins(v, &v->insets);\n    if (insets != null) { *insets = i; }\n    if (r != null) {\n        *r = (ui_rect_t) {\n            .x = v->x + i.left,\n            .y = v->y + i.top,\n            .w = v->w - i.left - i.right,\n            .h = v->h - i.top  - i.bottom,\n        };\n    }\n}\n\nstatic void ui_view_outbox(const ui_view_t* v, ui_rect_t* r, ui_ltrb_t* padding) {\n    rt_swear(r != null || padding != null);\n    rt_swear(v->max_w >= 0 && v->max_h >= 0);\n    const ui_ltrb_t p = ui_view_margins(v, &v->padding);\n    if (padding != null) { *padding = p; }\n    if (r != null) {\n//      rt_println(\"%s %d,%d %dx%d %.1f %.1f %.1f %.1f\", v->p.text,\n//          v->x, v->y, v->w, v->h,\n//          v->padding.left, v->padding.top, v->padding.right, v->padding.bottom);\n        *r = (ui_rect_t) {\n            .x = v->x - p.left,\n            .y = v->y - p.top,\n            .w = v->w + p.left + p.right,\n            .h = v->h + p.top  + p.bottom,\n        };\n//      rt_println(\"%s %d,%d %dx%d\", v->p.text,\n//          r->x, r->y, r->w, r->h);\n    }\n}\n\nstatic void ui_view_update_shortcut(ui_view_t* v) {\n    if (ui_view.is_control(v) && v->type != ui_view_text &&\n        v->shortcut == 0x00) {\n        const char* s = ui_view.string(v);\n        const char* a = strchr(s, '&');\n        if (a != null && a[1] != 0 && a[1] != '&') {\n            // TODO: utf-8 shortcuts? possible\n            v->shortcut = a[1];\n        }\n    }\n}\n\nstatic void ui_view_set_text_va(ui_view_t* v, const char* format, va_list va) {\n    char t[rt_countof(v->p.text)];\n    rt_str.format_va(t, rt_countof(t), format, va);\n    char* s = v->p.text;\n    if (strcmp(s, t) != 0) {\n        int32_t n = (int32_t)strlen(t);\n        memcpy(s, t, (size_t)n + 1);\n        v->p.strid = 0;  // next call to nls() will localize it\n        ui_view_update_shortcut(v);\n        ui_app.request_layout();\n    }\n}\n\nstatic void ui_view_set_text(ui_view_t* v, const char* format, ...) {\n    va_list va;\n    va_start(va, format);\n    ui_view.set_text_va(v, format, va);\n    va_end(va);\n}\n\nstatic void ui_view_show_hint(ui_view_t* v, ui_view_t* hint) {\n    ui_view_call_init(hint);\n    ui_view.set_text(hint, v->hint);\n    ui_view.measure(hint);\n    int32_t x = v->x + v->w / 2 - hint->w / 2 + hint->fm->em.w / 4;\n    int32_t y = v->y + v->h + hint->fm->em.h / 4;\n    if (x + hint->w > ui_app.crc.w) {\n        x = ui_app.crc.w - hint->w - hint->fm->em.w / 2;\n    }\n    if (x < 0) { x = hint->fm->em.w / 2; }\n    if (y + hint->h > ui_app.crc.h) {\n        y = ui_app.crc.h - hint->h - hint->fm->em.h / 2;\n    }\n    if (y < 0) { y = hint->fm->em.h / 2; }\n    // show_tooltip will center horizontally\n    ui_app.show_hint(hint, x + hint->w / 2, y, 0);\n}\n\nstatic void ui_view_hovering(ui_view_t* v, bool start) {\n    static ui_label_t hint = ui_label(0.0, \"\");\n    if (start && ui_app.animating.view == null && v->hint[0] != 0 &&\n       !ui_view.is_hidden(v)) {\n        hint.padding = (ui_margins_t){0, 0, 0, 0};\n        hint.parent = ui_app.content;\n        hint.state.hidden = false;\n        ui_view_show_hint(v, &hint);\n    } else if (!start && ui_app.animating.view == &hint) {\n        ui_app.show_hint(null, -1, -1, 0);\n    }\n}\n\nstatic bool ui_view_is_shortcut_key(ui_view_t* v, int64_t key) {\n    // Supported keyboard shortcuts are ASCII characters only for now\n    // If there is not focused UI control in Alt+key [Alt] is optional.\n    // If there is focused control only Alt+Key is accepted as shortcut\n    char ch = 0x20 <= key && key <= 0x7F ? (char)toupper((char)key) : 0x00;\n    bool needs_alt = ui_app.focus != null && ui_app.focus != v &&\n         !ui_view.is_parent_of(ui_app.focus, v);\n    bool keyboard_shortcut = ch != 0x00 && v->shortcut != 0x00 &&\n         (ui_app.alt || ui_app.ctrl || !needs_alt) && toupper(v->shortcut) == ch;\n    return keyboard_shortcut;\n}\n\nstatic bool ui_view_is_orphan(const ui_view_t* v) {\n    while (v != ui_app.root && v != null) { v = v->parent; }\n    return v == null;\n}\n\nstatic bool ui_view_is_hidden(const ui_view_t* v) {\n    bool hidden = v->state.hidden || ui_view.is_orphan(v);\n    while (!hidden && v->parent != null) {\n        v = v->parent;\n        hidden = v->state.hidden;\n    }\n    return hidden;\n}\n\nstatic bool ui_view_is_disabled(const ui_view_t* v) {\n    bool disabled = v->state.disabled;\n    while (!disabled && v->parent != null) {\n        v = v->parent;\n        disabled = v->state.disabled;\n    }\n    return disabled;\n}\n\nstatic void ui_view_timer(ui_view_t* v, ui_timer_t id) {\n    if (v->timer != null) { v->timer(v, id); }\n    // timers are delivered even to hidden and disabled views:\n    ui_view_for_each(v, c, { ui_view_timer(c, id); });\n}\n\nstatic void ui_view_every_sec(ui_view_t* v) {\n    if (v->every_sec != null) { v->every_sec(v); }\n    ui_view_for_each(v, c, { ui_view_every_sec(c); });\n}\n\nstatic void ui_view_every_100ms(ui_view_t* v) {\n    if (v->every_100ms != null) { v->every_100ms(v); }\n    ui_view_for_each(v, c, { ui_view_every_100ms(c); });\n}\n\nstatic bool ui_view_key_pressed(ui_view_t* v, int64_t k) {\n    bool done = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->key_pressed != null) {\n            ui_view_update_shortcut(v);\n            done = v->key_pressed(v, k);\n        }\n        if (!done) {\n            ui_view_for_each(v, c, {\n                done = ui_view_key_pressed(c, k);\n                if (done) { break; }\n            });\n        }\n    }\n    return done;\n}\n\nstatic bool ui_view_key_released(ui_view_t* v, int64_t k) {\n    bool done = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->key_released != null) {\n            done = v->key_released(v, k);\n        }\n        if (!done) {\n            ui_view_for_each(v, c, {\n                done = ui_view_key_released(c, k);\n                if (done) { break; }\n            });\n        }\n    }\n    return done;\n}\n\nstatic void ui_view_character(ui_view_t* v, const char* utf8) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->character != null) {\n            ui_view_update_shortcut(v);\n            v->character(v, utf8);\n        }\n        ui_view_for_each(v, c, { ui_view_character(c, utf8); });\n    }\n}\n\nstatic void ui_view_resolve_color_ids(ui_view_t* v) {\n    if (v->color_id > 0) {\n        v->color = ui_colors.get_color(v->color_id);\n    }\n    if (v->background_id > 0) {\n        v->background = ui_colors.get_color(v->background_id);\n    }\n}\n\nstatic void ui_view_paint(ui_view_t* v) {\n    rt_assert(ui_app.crc.w > 0 && ui_app.crc.h > 0);\n    ui_view_resolve_color_ids(v);\n    if (v->debug.trace.prc) {\n        const char* s = ui_view.string(v);\n        rt_println(\"%d,%d %dx%d prc: %d,%d %dx%d \\\"%.*s\\\"\", v->x, v->y, v->w, v->h,\n                ui_app.prc.x, ui_app.prc.y, ui_app.prc.w, ui_app.prc.h,\n                rt_min(64, strlen(s)), s);\n    }\n    if (!v->state.hidden && ui_app.crc.w > 0 && ui_app.crc.h > 0) {\n        if (v->erase   != null) { v->erase(v); }\n        if (v->paint   != null) { v->paint(v); }\n        if (v->painted != null) { v->painted(v); }\n        if (v->debug.paint.margins) { ui_view.debug_paint_margins(v); }\n        if (v->debug.paint.fm)   { ui_view.debug_paint_fm(v); }\n        if (v->debug.paint.call && v->debug_paint != null) { v->debug_paint(v); }\n        ui_view_for_each(v, c, { ui_view_paint(c); });\n    }\n}\n\nstatic bool ui_view_has_focus(const ui_view_t* v) {\n    return ui_app.focused() && ui_app.focus == v;\n}\n\nstatic void ui_view_set_focus(ui_view_t* v) {\n    if (ui_app.focus != v) {\n        ui_view_t* loosing = ui_app.focus;\n        ui_view_t* gaining = v;\n        if (gaining != null) {\n            rt_swear(gaining->focusable && !ui_view.is_hidden(gaining) &&\n                                        !ui_view.is_disabled(gaining));\n        }\n        if (loosing != null) { rt_swear(loosing->focusable); }\n        ui_app.focus = v;\n        if (loosing != null && loosing->focus_lost != null) {\n            loosing->focus_lost(loosing);\n        }\n        if (gaining != null && gaining->focus_gained != null) {\n            gaining->focus_gained(gaining);\n        }\n    }\n}\n\nstatic int64_t ui_view_hit_test(const ui_view_t* v, ui_point_t pt) {\n    int64_t ht = ui.hit_test.nowhere;\n    if (!ui_view.is_hidden(v) && v->hit_test != null) {\n         ht = v->hit_test(v, pt);\n    }\n    if (ht == ui.hit_test.nowhere) {\n        ui_view_for_each(v, c, {\n            if (!c->state.hidden && ui_view.inside(c, &pt)) {\n                ht = ui_view_hit_test(c, pt);\n                if (ht != ui.hit_test.nowhere) { break; }\n            }\n        });\n    }\n    return ht;\n}\n\nstatic void ui_view_update_hover(ui_view_t* v, bool hidden) {\n    const bool hover  = v->state.hover;\n    const bool inside = ui_view.inside(v, &ui_app.mouse);\n    v->state.hover = !ui_view.is_hidden(v) && inside;\n    if (hover != v->state.hover) {\n//      rt_println(\"hover := %d %p %s\", v->state.hover, v, ui_view_debug_id(v));\n        ui_view.hover_changed(v); // even for hidden\n        if (!hidden) { ui_view.invalidate(v, null); }\n    }\n}\n\nstatic void ui_view_mouse_hover(ui_view_t* v) {\n//  rt_println(\"%d,%d %s\", ui_app.mouse.x, ui_app.mouse.y,\n//          ui_app.mouse_left  ? \"L\" : \"_\",\n//          ui_app.mouse_right ? \"R\" : \"_\");\n    // mouse hover over is dispatched even to disabled views\n    const bool hidden = ui_view.is_hidden(v);\n    ui_view_update_hover(v, hidden);\n    if (!hidden && v->mouse_hover != null) { v->mouse_hover(v); }\n    ui_view_for_each(v, c, { ui_view_mouse_hover(c); });\n}\n\nstatic void ui_view_mouse_move(ui_view_t* v) {\n//  rt_println(\"%d,%d %s\", ui_app.mouse.x, ui_app.mouse.y,\n//          ui_app.mouse_left  ? \"L\" : \"_\",\n//          ui_app.mouse_right ? \"R\" : \"_\");\n    // mouse move is dispatched even to disabled views\n    const bool hidden = ui_view.is_hidden(v);\n    ui_view_update_hover(v, hidden);\n    if (!hidden && v->mouse_move != null) { v->mouse_move(v); }\n    ui_view_for_each(v, c, { ui_view_mouse_move(c); });\n}\n\nstatic void ui_view_double_click(ui_view_t* v, int32_t ix) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (inside) {\n            if (v->focusable) { ui_view.set_focus(v); }\n            if (v->double_click != null) { v->double_click(v, ix); }\n        }\n        ui_view_for_each(v, c, { ui_view_double_click(c, ix); });\n    }\n}\n\nstatic void ui_view_mouse_scroll(ui_view_t* v, ui_point_t dx_dy) {\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        if (v->mouse_scroll != null) { v->mouse_scroll(v, dx_dy); }\n        ui_view_for_each(v, c, { ui_view_mouse_scroll(c, dx_dy); });\n    }\n}\n\nstatic void ui_view_hover_changed(ui_view_t* v) {\n    if (!v->state.hidden) {\n        if (!v->state.hover) {\n            v->p.hover_when = 0;\n            ui_view.hovering(v, false); // cancel hover\n        } else {\n            rt_swear(ui_view_hover_delay >= 0);\n            if (v->p.hover_when >= 0) {\n                v->p.hover_when = ui_app.now + ui_view_hover_delay;\n            }\n        }\n    }\n}\n\nstatic void ui_view_lose_hidden_focus(ui_view_t* v) {\n    // removes focus from hidden or disabled ui controls\n    if (ui_app.focus != null) {\n        if (ui_app.focus == v && (v->state.disabled || v->state.hidden)) {\n            ui_view.set_focus(null);\n        } else {\n            ui_view_for_each(v, c, {\n                if (ui_app.focus != null) { ui_view_lose_hidden_focus(c); }\n            });\n        }\n    }\n}\n\nstatic bool ui_view_tap(ui_view_t* v, int32_t ix, bool pressed) {\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_tap(c, ix, pressed);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && pressed && inside) {\n            if (v->focusable) { ui_view.set_focus(v); }\n            if (v->tap != null) { swallow = v->tap(v, ix, pressed); }\n        }\n        if (!swallow && !pressed) {\n            // mouse click release is never swallowed because a lot\n            // of controls want to hear it:\n            if (v->tap != null) { (void)v->tap(v, ix, pressed); }\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_long_press(ui_view_t* v, int32_t ix) {\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_long_press(c, ix);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->long_press != null) {\n            swallow = v->long_press(v, ix);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_double_tap(ui_view_t* v, int32_t ix) { // 0: left 1: middle 2: right\n    bool swallow = false; // consumed\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_double_tap(c, ix);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->double_tap != null) {\n            swallow = v->double_tap(v, ix);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_context_menu(ui_view_t* v) {\n    bool swallow = false;\n    if (!ui_view.is_hidden(v) && !ui_view.is_disabled(v)) {\n        ui_view_for_each(v, c, {\n            swallow = ui_view_context_menu(c);\n            if (swallow) { break; }\n        });\n        const bool inside = ui_view.inside(v, &ui_app.mouse);\n        if (!swallow && inside && v->context_menu != null) {\n            swallow = v->context_menu(v);\n        }\n    }\n    return swallow;\n}\n\nstatic bool ui_view_message(ui_view_t* view, int32_t m, int64_t wp, int64_t lp,\n        int64_t* ret) {\n    if (!view->state.hidden) {\n        if (view->p.hover_when > 0 && ui_app.now > view->p.hover_when) {\n            view->p.hover_when = -1; // \"already called\"\n            ui_view.hovering(view, true);\n        }\n    }\n    // message() callback is called even for hidden and disabled views\n    // could be useful for enabling conditions of post() messages from\n    // background rt_thread.\n    if (view->message != null) {\n        if (view->message(view, m, wp, lp, ret)) { return true; }\n    }\n    ui_view_for_each(view, c, {\n        if (ui_view_message(c, m, wp, lp, ret)) { return true; }\n    });\n    return false;\n}\n\nstatic bool ui_view_is_container(const ui_view_t* v) {\n    return  v->type == ui_view_stack ||\n            v->type == ui_view_span  ||\n            v->type == ui_view_list;\n}\n\nstatic bool ui_view_is_spacer(const ui_view_t* v) {\n    return  v->type == ui_view_spacer;\n}\n\nstatic bool ui_view_is_control(const ui_view_t* v) {\n    return  v->type == ui_view_text   ||\n            v->type == ui_view_label  ||\n            v->type == ui_view_toggle ||\n            v->type == ui_view_button ||\n            v->type == ui_view_slider ||\n            v->type == ui_view_mbx;\n}\n\nstatic void ui_view_debug_paint_margins(ui_view_t* v) {\n    if (v->debug.paint.margins) {\n        if (v->type == ui_view_spacer) {\n            ui_gdi.fill(v->x, v->y, v->w, v->h, ui_color_rgb(128, 128, 128));\n        }\n        const ui_ltrb_t p = ui_view.margins(v, &v->padding);\n        const ui_ltrb_t i = ui_view.margins(v, &v->insets);\n        ui_color_t c = ui_colors.green;\n        const int32_t pl = p.left;\n        const int32_t pr = p.right;\n        const int32_t pt = p.top;\n        const int32_t pb = p.bottom;\n        if (pl > 0) { ui_gdi.frame(v->x - pl, v->y, pl, v->h, c); }\n        if (pr > 0) { ui_gdi.frame(v->x + v->w, v->y, pr, v->h, c); }\n        if (pt > 0) { ui_gdi.frame(v->x, v->y - pt, v->w, pt, c); }\n        if (p.bottom > 0) {\n            ui_gdi.frame(v->x, v->y + v->h, v->w, pb, c);\n        }\n        c = ui_colors.orange;\n        const int32_t il = i.left;\n        const int32_t ir = i.right;\n        const int32_t it = i.top;\n        const int32_t ib = i.bottom;\n        if (il > 0) { ui_gdi.frame(v->x, v->y, il, v->h, c); }\n        if (ir > 0) { ui_gdi.frame(v->x + v->w - ir, v->y, ir, v->h, c); }\n        if (it > 0) { ui_gdi.frame(v->x, v->y, v->w, it, c); }\n        if (ib > 0) { ui_gdi.frame(v->x, v->y + v->h - ib, v->w, ib, c); }\n        if ((ui_view.is_container(v) || ui_view.is_spacer(v)) &&\n            v->w > 0 && v->h > 0) {\n            ui_wh_t wh = ui_view_text_metrics(v->x, v->y, false, 0,\n                                              v->fm, \"%s\", ui_view.string(v));\n            const int32_t tx = v->x;\n            const int32_t ty = v->y + v->h - wh.h;\n            const ui_gdi_ta_t ta = { .fm = v->fm, .color = ui_colors.red };\n            ui_gdi.text(&ta, tx, ty, \"%s %d,%d %dx%d\", ui_view_debug_id(v),\n                        v->x, v->y, v->w, v->h);\n        }\n    }\n}\n\nstatic void ui_view_debug_paint_fm(ui_view_t* v) {\n    if (v->debug.paint.fm && v->p.text[0] != 0 &&\n       !ui_view_is_container(v) && !ui_view_is_spacer(v)) {\n        const ui_point_t t = v->text.xy;\n        const int32_t x = v->x;\n        const int32_t y = v->y;\n        const int32_t w = v->w;\n        const int32_t y_0 = y + t.y;\n        const int32_t y_b = y_0 + v->fm->baseline;\n        const int32_t y_a = y_b - v->fm->ascent;\n        const int32_t y_h = y_0 + v->fm->height;\n        const int32_t y_x = y_b - v->fm->x_height;\n        const int32_t y_d = y_b + v->fm->descent;\n        // fm.height y == 0 line is painted one pixel higher:\n        ui_gdi.line(x, y_0 - 1, x + w, y_0 - 1, ui_colors.red);\n        ui_gdi.line(x, y_a, x + w, y_a, ui_colors.green);\n        ui_gdi.line(x, y_x, x + w, y_x, ui_colors.orange);\n        ui_gdi.line(x, y_b, x + w, y_b, ui_colors.red);\n        ui_gdi.line(x, y_d, x + w, y_d, ui_colors.green);\n        if (y_h != y_d) {\n            ui_gdi.line(x, y_d, x + w, y_d, ui_colors.green);\n            ui_gdi.line(x, y_h, x + w, y_h, ui_colors.red);\n        } else {\n            ui_gdi.line(x, y_h, x + w, y_h, ui_colors.orange);\n        }\n        // fm.height line painted _under_ the actual height\n    }\n}\n\n#pragma push_macro(\"ui_view_no_siblings\")\n\n#define ui_view_no_siblings(v) do {                    \\\n    rt_swear((v)->parent == null && (v)->child == null && \\\n          (v)->prev == null && (v)->next == null);     \\\n} while (0)\n\nstatic void ui_view_test(void) {\n    ui_view_t p0 = ui_view(stack);\n    ui_view_t c1 = ui_view(stack);\n    ui_view_t c2 = ui_view(stack);\n    ui_view_t c3 = ui_view(stack);\n    ui_view_t c4 = ui_view(stack);\n    ui_view_t g1 = ui_view(stack);\n    ui_view_t g2 = ui_view(stack);\n    ui_view_t g3 = ui_view(stack);\n    ui_view_t g4 = ui_view(stack);\n    // add grand children to children:\n    ui_view.add(&c2, &g1, &g2, null);               ui_view_verify(&c2);\n    ui_view.add(&c3, &g3, &g4, null);               ui_view_verify(&c3);\n    // single child\n    ui_view.add(&p0, &c1, null);                    ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    // two children\n    ui_view.add(&p0, &c1, &c2, null);               ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    // three children\n    ui_view.add(&p0, &c1, &c2, &c3, null);          ui_view_verify(&p0);\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    ui_view.remove(&c3);                            ui_view_verify(&p0);\n    // add_first, add_last, add_before, add_after\n    ui_view.add_first(&p0, &c1);                    ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    ui_view.add_last(&p0, &c4);                     ui_view_verify(&p0);\n    rt_swear(p0.child == &c1 && p0.child->prev == &c4);\n    ui_view.add_after(&c2, &c1);                    ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    rt_swear(c1.next == &c2);\n    ui_view.add_before(&c3, &c4);                   ui_view_verify(&p0);\n    rt_swear(p0.child == &c1);\n    rt_swear(c4.prev == &c3);\n    // removing all\n    ui_view.remove(&c1);                            ui_view_verify(&p0);\n    ui_view.remove(&c2);                            ui_view_verify(&p0);\n    ui_view.remove(&c3);                            ui_view_verify(&p0);\n    ui_view.remove(&c4);                            ui_view_verify(&p0);\n    ui_view_no_siblings(&p0);\n    ui_view_no_siblings(&c1);\n    ui_view_no_siblings(&c4);\n    ui_view.remove(&g1);                            ui_view_verify(&c2);\n    ui_view.remove(&g2);                            ui_view_verify(&c2);\n    ui_view.remove(&g3);                            ui_view_verify(&c3);\n    ui_view.remove(&g4);                            ui_view_verify(&c3);\n    ui_view_no_siblings(&c2); ui_view_no_siblings(&c3);\n    ui_view_no_siblings(&g1); ui_view_no_siblings(&g2);\n    ui_view_no_siblings(&g3); ui_view_no_siblings(&g4);\n    // a bit more intuitive (for a human) nested way to initialize tree:\n    ui_view.add(&p0,\n        &c1,\n        ui_view.add(&c2, &g1, &g2, null),\n        ui_view.add(&c3, &g3, &g4, null),\n        &c4);\n    ui_view_verify(&p0);\n    ui_view_disband(&p0);\n    ui_view_no_siblings(&p0);\n    ui_view_no_siblings(&c1); ui_view_no_siblings(&c2);\n    ui_view_no_siblings(&c3); ui_view_no_siblings(&c4);\n    ui_view_no_siblings(&g1); ui_view_no_siblings(&g2);\n    ui_view_no_siblings(&g3); ui_view_no_siblings(&g4);\n    if (rt_debug.verbosity.level > rt_debug.verbosity.quiet) { rt_println(\"done\"); }\n}\n\n#pragma pop_macro(\"ui_view_no_siblings\")\n\nui_view_if ui_view = {\n    .add                 = ui_view_add,\n    .add_first           = ui_view_add_first,\n    .add_last            = ui_view_add_last,\n    .add_after           = ui_view_add_after,\n    .add_before          = ui_view_add_before,\n    .remove              = ui_view_remove,\n    .remove_all          = ui_view_remove_all,\n    .disband             = ui_view_disband,\n    .inside              = ui_view_inside,\n    .is_parent_of        = ui_view_is_parent_of,\n    .margins             = ui_view_margins,\n    .inbox               = ui_view_inbox,\n    .outbox              = ui_view_outbox,\n    .set_text            = ui_view_set_text,\n    .set_text_va         = ui_view_set_text_va,\n    .invalidate          = ui_view_invalidate,\n    .text_metrics_va     = ui_view_text_metrics_va,\n    .text_metrics        = ui_view_text_metrics,\n    .text_measure        = ui_view_text_measure,\n    .text_align          = ui_view_text_align,\n    .measure_control     = ui_view_measure_control,\n    .measure_children    = ui_view_measure_children,\n    .layout_children     = ui_view_layout_children,\n    .measure             = ui_view_measure,\n    .layout              = ui_view_layout,\n    .string              = ui_view_string,\n    .is_orphan           = ui_view_is_orphan,\n    .is_hidden           = ui_view_is_hidden,\n    .is_disabled         = ui_view_is_disabled,\n    .is_control          = ui_view_is_control,\n    .is_container        = ui_view_is_container,\n    .is_spacer           = ui_view_is_spacer,\n    .timer               = ui_view_timer,\n    .every_sec           = ui_view_every_sec,\n    .every_100ms         = ui_view_every_100ms,\n    .hit_test            = ui_view_hit_test,\n    .key_pressed         = ui_view_key_pressed,\n    .key_released        = ui_view_key_released,\n    .character           = ui_view_character,\n    .paint               = ui_view_paint,\n    .has_focus           = ui_view_has_focus,\n    .set_focus           = ui_view_set_focus,\n    .lose_hidden_focus   = ui_view_lose_hidden_focus,\n    .mouse_hover         = ui_view_mouse_hover,\n    .mouse_move          = ui_view_mouse_move,\n    .mouse_scroll        = ui_view_mouse_scroll,\n    .hovering            = ui_view_hovering,\n    .hover_changed       = ui_view_hover_changed,\n    .is_shortcut_key     = ui_view_is_shortcut_key,\n    .context_menu        = ui_view_context_menu,\n    .tap                 = ui_view_tap,\n    .long_press          = ui_view_long_press,\n    .double_tap          = ui_view_double_tap,\n    .message             = ui_view_message,\n    .debug_paint_margins = ui_view_debug_paint_margins,\n    .debug_paint_fm      = ui_view_debug_paint_fm,\n    .test                = ui_view_test\n};\n\n#ifdef UI_VIEW_TEST\n\nrt_static_init(ui_view) {\n    ui_view.test();\n}\n\n#endif\n\n#pragma pop_macro(\"ui_view_for_each\")\n"
  },
  {
    "path": "test/test1.c",
    "content": "#include \"rt/rt.h\"\n#include <stdio.h>\n\nstatic int usage(void) {\n    fprintf(stderr, \"Usage: %s [options]\\n\", rt_args.basename());\n    fprintf(stderr, \"Options:\\n\");\n    fprintf(stderr, \"  --help, -h     - this help\\n\");\n    fprintf(stderr, \"  --verbosity    - set verbosity level \"\n                                \"(quiet, info, verbose, debug, trace)\\n\");\n    fprintf(stderr, \"  --verbose, -v  - set verbosity level to verbose\\n\");\n    return 0;\n}\n\nstatic int run(void) {\n    if (rt_args.option_bool(\"--help\") || rt_args.option_bool(\"-?\")) {\n        return usage();\n    }\n    const char* v = rt_args.option_str(\"--verbosity\");\n    if (v != null) {\n        rt_debug.verbosity.level = rt_debug.verbosity_from_string(v);\n    } else if (rt_args.option_bool(\"-v\") || rt_args.option_bool(\"--verbose\")) {\n        rt_debug.verbosity.level = rt_debug.verbosity.verbose;\n    }\n    rt_core.test();\n    rt_println(\"all tests passed\\n\\n\");\n//  rt_println(\"rt_args.basename(): %s\", rt_args.basename());\n//  rt_println(\"rt_args.v[0]: %s\", rt_args.v[0]);\n//  for (int i = 1; i < rt_args.c; i++) {\n//      rt_println(\"rt_args.v[%d]: %s\", i, rt_args.v[i]);\n//  }\n    //  $ .\\bin\\debug\\test1.exe \"Hello World\" Hello World\n    //  rt_args.v[0]: .\\bin\\debug\\test1.exe\n    //  rt_args.basename(): test1\n    //  rt_args.v[1]: Hello World\n    //  rt_args.v[2]: Hello\n    //  rt_args.v[3]: World\n    return 0;\n}\n\n// both main() wand WinMain() can be present and compiled.\n// Runtime does something along the lines:\n// #include <winnt.h>\n// #include <dbghelp.h>\n//   IMAGE_NT_HEADERS32* h = ImageNtHeader(GetModuleHandle(null));\n//   h->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI\n//   h->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI\n// to select and call appropriate function:\n\nint main(int argc, const char* argv[], const char *envp[]) {\n    rt_args.main(argc, argv, envp);\n    int r = run();\n    rt_args.fini();\n    return r;\n}\n\n#include \"rt/rt_win32.h\"\n\n#pragma warning(suppress: 28251) // no annotations\n\nint APIENTRY WinMain(HINSTANCE rt_unused(inst), HINSTANCE rt_unused(prev),\n                     char* rt_unused(command), int rt_unused(show)) {\n    rt_args.WinMain(); // Uses GetCommandLineW() which has full pathname\n    int r = run();\n    rt_args.fini();\n    return r;\n}\n"
  },
  {
    "path": "test/test1.rc",
    "content": "﻿#include <winres.h>\n\n#define company_name \"<Insert Name Here>\"\n#define copyright \"Copyright (C) 2024 `Leo` Dmitry Kuznetsov. All rights reserved.\"\n#define file_description \"https://github.com/leok7v/ut\"\n#define original_file_name \"test1.exe\"\n#define product_name \"test1\"\n\n#include \"..\\inc\\rt\\version.rc.in\"\n\n"
  },
  {
    "path": "test/test2.c",
    "content": "#define rt_implementation\n#include \"single_file_lib/rt/rt.h\"\n\nint main(int argc, char* argv[], char *envp[]) {\n    rt_args.main(argc, argv, envp);\n    const char* v = rt_args.option_str(\"--verbosity\");\n    if (v != null) {\n        rt_debug.verbosity.level = rt_debug.verbosity_from_string(v);\n    } else if (rt_args.option_bool(\"-v\") || rt_args.option_bool(\"--verbose\")) {\n        rt_debug.verbosity.level = rt_debug.verbosity.info;\n    }\n    rt_core.test();\n    rt_println(\"all tests passed\\n\");\n    rt_args.fini();\n    return 0;\n}\n"
  }
]