diff options
| author | Aldrik Ramaekers <aldrik.ramaekers@protonmail.com> | 2020-01-30 21:11:12 +0100 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrik.ramaekers@protonmail.com> | 2020-01-30 21:11:12 +0100 |
| commit | 260f05025631031b7cc4904805d5017feaf53eda (patch) | |
| tree | d5a723bb7bbbc9f8b598712723fe3d8290c0a54c | |
initial commit
| -rw-r--r-- | .gitignore | 7 | ||||
| -rw-r--r-- | COPYING | 25 | ||||
| -rw-r--r-- | README.md | 22 | ||||
| -rw-r--r-- | build-linux.sh | 64 | ||||
| -rw-r--r-- | build-win.bat | 22 | ||||
| -rw-r--r-- | data/config.txt | 10 | ||||
| -rw-r--r-- | data/fonts/mono.ttf | bin | 0 -> 313408 bytes | |||
| -rw-r--r-- | data/imgs/delete.png | bin | 0 -> 451 bytes | |||
| -rw-r--r-- | data/imgs/en.png | bin | 0 -> 1132 bytes | |||
| -rw-r--r-- | data/imgs/error.png | bin | 0 -> 980 bytes | |||
| -rw-r--r-- | data/imgs/exclaim.png | bin | 0 -> 201 bytes | |||
| -rw-r--r-- | data/imgs/folder.png | bin | 0 -> 1203 bytes | |||
| -rw-r--r-- | data/imgs/list.png | bin | 0 -> 140 bytes | |||
| -rw-r--r-- | data/imgs/logo_64.png | bin | 0 -> 399 bytes | |||
| -rw-r--r-- | data/imgs/nl.png | bin | 0 -> 509 bytes | |||
| -rw-r--r-- | data/imgs/search.png | bin | 0 -> 1624 bytes | |||
| -rw-r--r-- | data/marketing/cover_image.png | bin | 0 -> 6568 bytes | |||
| -rw-r--r-- | data/marketing/logo_32.png | bin | 0 -> 294 bytes | |||
| -rw-r--r-- | data/marketing/logo_512.ico | bin | 0 -> 101503 bytes | |||
| -rw-r--r-- | data/marketing/logo_512.png | bin | 0 -> 5857 bytes | |||
| -rw-r--r-- | data/marketing/logo_64.png | bin | 0 -> 399 bytes | |||
| -rw-r--r-- | data/marketing/main.png | bin | 0 -> 52312 bytes | |||
| -rw-r--r-- | data/marketing/main2.png | bin | 0 -> 78881 bytes | |||
| -rw-r--r-- | data/marketing/settings1.png | bin | 0 -> 21262 bytes | |||
| -rw-r--r-- | data/marketing/settings2.png | bin | 0 -> 15750 bytes | |||
| -rw-r--r-- | data/marketing/text-search_banner.png | bin | 0 -> 17233 bytes | |||
| -rw-r--r-- | data/translations/en-English.mo | bin | 0 -> 4894 bytes | |||
| -rw-r--r-- | data/translations/nl-Dutch.mo | bin | 0 -> 5152 bytes | |||
| -rw-r--r-- | install.sh | 87 | ||||
| -rw-r--r-- | misc/icon.rc | 28 | ||||
| -rw-r--r-- | misc/icon.res | bin | 0 -> 102850 bytes | |||
| -rw-r--r-- | misc/logo_512.ico | bin | 0 -> 101503 bytes | |||
| -rw-r--r-- | src/array.c | 204 | ||||
| -rw-r--r-- | src/array.h | 32 | ||||
| -rw-r--r-- | src/assets.c | 313 | ||||
| -rw-r--r-- | src/assets.h | 149 | ||||
| -rw-r--r-- | src/camera.c | 22 | ||||
| -rw-r--r-- | src/camera.h | 19 | ||||
| -rw-r--r-- | src/command_line.c | 265 | ||||
| -rw-r--r-- | src/command_line.h | 12 | ||||
| -rw-r--r-- | src/config.h | 18 | ||||
| -rw-r--r-- | src/external/LooplessSizeMove.c | 864 | ||||
| -rw-r--r-- | src/external/cJSON.c | 2980 | ||||
| -rw-r--r-- | src/external/cJSON.h | 294 | ||||
| -rw-r--r-- | src/external/stb_image.h | 7547 | ||||
| -rw-r--r-- | src/external/stb_truetype.h | 4882 | ||||
| -rw-r--r-- | src/external/utf8.h | 1258 | ||||
| -rw-r--r-- | src/input.c | 237 | ||||
| -rw-r--r-- | src/input.h | 224 | ||||
| -rw-r--r-- | src/languages.h | 22 | ||||
| -rw-r--r-- | src/linux/platform.c | 1592 | ||||
| -rw-r--r-- | src/linux/thread.c | 129 | ||||
| -rw-r--r-- | src/localization.c | 149 | ||||
| -rw-r--r-- | src/localization.h | 59 | ||||
| -rw-r--r-- | src/memory.h | 19 | ||||
| -rw-r--r-- | src/memory_bucket.c | 74 | ||||
| -rw-r--r-- | src/memory_bucket.h | 26 | ||||
| -rw-r--r-- | src/mo_edit.c | 531 | ||||
| -rw-r--r-- | src/platform.h | 221 | ||||
| -rw-r--r-- | src/platform_shared.c | 244 | ||||
| -rw-r--r-- | src/project_base.h | 111 | ||||
| -rw-r--r-- | src/render.c | 446 | ||||
| -rw-r--r-- | src/render.h | 61 | ||||
| -rw-r--r-- | src/save.c | 380 | ||||
| -rw-r--r-- | src/save.h | 16 | ||||
| -rw-r--r-- | src/settings.c | 313 | ||||
| -rw-r--r-- | src/settings.h | 60 | ||||
| -rw-r--r-- | src/settings_config.c | 240 | ||||
| -rw-r--r-- | src/settings_config.h | 40 | ||||
| -rw-r--r-- | src/string_utils.c | 551 | ||||
| -rw-r--r-- | src/string_utils.h | 87 | ||||
| -rw-r--r-- | src/thread.h | 67 | ||||
| -rw-r--r-- | src/ui.c | 1481 | ||||
| -rw-r--r-- | src/ui.h | 205 | ||||
| -rw-r--r-- | src/windows/platform.c | 1267 | ||||
| -rw-r--r-- | src/windows/thread.c | 102 |
76 files changed, 28078 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bde577f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +release/ +art/ +project.4coder +src/.gitignore +generate-graph.sh +download-translations.sh
\ No newline at end of file @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2019, Aldrik Ramaekers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e08cbc4 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# text-search +Text-search is a GUI Program to find files and text within files for Linux. <br> +text-search is a single and small executable. + +# Requirements + +### Linux +- GCC +- libglu1-mesa-dev, libgl1-mesa-dev (automatically installed with build/install script) +- ld + +## Windows (not working) +- MinGW32 +- Windres +- ld + +# Build/Install +run __build-linux.sh -r__ or __build-win.sh -r__ for building and running a debug build<br /> +run __install.sh__ as root to install to __/usr/local/bin/text-search__ or __C:\Users\\\<user>\\Desktop\\text-search.exe__ <br /> + +# Config +config.txt is stored at __~/.config/text-search/config.txt__ or __C:\Users\\\<user>\Local Settings\Application Data\text-search\config.txt__ diff --git a/build-linux.sh b/build-linux.sh new file mode 100644 index 0000000..2a6d2c1 --- /dev/null +++ b/build-linux.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +if [ $(dpkg-query -W -f='${Status}' libglu1-mesa-dev 2>/dev/null | grep -c "ok installed") -eq 0 ]; +then + if [ "$EUID" -ne 0 ] + then + echo "Missing dependency: libglu1-mesa-dev, install this package or run this script as root" + else + apt-get install libglu1-mesa-dev; + fi +fi + +if [ $(dpkg-query -W -f='${Status}' libgl1-mesa-dev 2>/dev/null | grep -c "ok installed") -eq 0 ]; +then + if [ "$EUID" -ne 0 ] + then + echo "Missing dependency: libgl1-mesa-dev, install this package or run this script as root" + else + apt-get install libgl1-mesa-dev; + fi +fi + +if [ $(dpkg-query -W -f='${Status}' libxrandr-dev 2>/dev/null | grep -c "ok installed") -eq 0 ]; +then + if [ "$EUID" -ne 0 ] + then + echo "Missing dependency: libxrandr-dev, install this package or run this script as root" + else + apt-get install libxrandr-dev; + fi +fi + +rm -rf bin +mkdir bin +cd src + +ld -r -b binary -o ../bin/data.o \ +../data/imgs/en.png \ +../data/imgs/error.png \ +../data/imgs/folder.png \ +../data/imgs/nl.png \ +../data/imgs/search.png \ +../data/imgs/logo_64.png \ +../data/fonts/mono.ttf \ +../data/translations/en-English.mo \ +../data/translations/nl-Dutch.mo \ + +gcc -Wall -g -m64 -DMODE_DEVELOPER -Wno-unused-label -rdynamic -Wno-unused-variable text_search.c ../bin/data.o -o ../bin/text-search -lX11 -lGL -lGLU -lXrandr -lm -lpthread -ldl + +rm -f ../bin/data.o + +if [ $? -ne 0 ]; then + cd ../ + exit 1 +fi + +cd ../ + +if [ "$1" == "-r" ]; then + cd bin + ./text-search +# ./text-search --directory "/home/aldrik/Projects/text-search" --filter "*.c,*.h" --text "TODO(Aldrik)" --max-file-size 200 --locale "nl" --threads 5 + cd .. +fi diff --git a/build-win.bat b/build-win.bat new file mode 100644 index 0000000..13cbace --- /dev/null +++ b/build-win.bat @@ -0,0 +1,22 @@ +@echo off + +windres misc/icon.rc -O coff -o misc/icon.res + +DEL /S /Q bin +cd src + +ld -r -b binary -o ../bin/data.o ../data/imgs/en.png ../data/imgs/error.png ../data/imgs/folder.png ../data/imgs/nl.png ../data/imgs/search.png ../data/imgs/logo_64.png ../data/fonts/mono.ttf ../data/translations/en-English.mo ../data/translations/nl-Dutch.mo ../data/imgs/list.png ../data/imgs/delete.png ../data/imgs/exclaim.png + +if "%1"=="-w" (SET defs=-DMODE_DEVELOPER -DMODE_GDBDEBUG) else (SET defs=-DMODE_DEVELOPER) + +x86_64-w64-mingw32-gcc -m64 -Wall -g %defs% -Wno-unused-label -Wno-unused-variable mo_edit.c ../bin/data.o -o ../bin/moedit.exe ../misc/icon.res -lopengl32 -lkernel32 -lglu32 -lgdi32 -lcomdlg32 -lgdiplus -lole32 -lshlwapi + +DEL /Q "../bin/data.o" + +FOR %%A IN ("../bin/moedit.exe") DO set size=%%~zA +echo size = %size% + +cd ../ + +if "%1"=="-r" start bin/moedit.exe +if "%1"=="-w" start gdb -ex run bin/moedit.exe
\ No newline at end of file diff --git a/data/config.txt b/data/config.txt new file mode 100644 index 0000000..c0d0264 --- /dev/null +++ b/data/config.txt @@ -0,0 +1,10 @@ +SEARCH_DIRECTORY = "/opt/textsearch/" +SEARCH_DIRECTORIES = "1" +SEARCH_TEXT = "*hello world*" +FILE_FILTER = "*.txt,*.c" +MAX_THEAD_COUNT = "20" +MAX_FILE_SIZE = "200" +WINDOW_WIDTH = "800" +WINDOW_HEIGHT = "600" +PARALLELIZE_SEARCH = "1" +LOCALE = "en" diff --git a/data/fonts/mono.ttf b/data/fonts/mono.ttf Binary files differnew file mode 100644 index 0000000..1a39bc7 --- /dev/null +++ b/data/fonts/mono.ttf diff --git a/data/imgs/delete.png b/data/imgs/delete.png Binary files differnew file mode 100644 index 0000000..fa0ab77 --- /dev/null +++ b/data/imgs/delete.png diff --git a/data/imgs/en.png b/data/imgs/en.png Binary files differnew file mode 100644 index 0000000..02fa2a1 --- /dev/null +++ b/data/imgs/en.png diff --git a/data/imgs/error.png b/data/imgs/error.png Binary files differnew file mode 100644 index 0000000..aacd682 --- /dev/null +++ b/data/imgs/error.png diff --git a/data/imgs/exclaim.png b/data/imgs/exclaim.png Binary files differnew file mode 100644 index 0000000..40cc4b6 --- /dev/null +++ b/data/imgs/exclaim.png diff --git a/data/imgs/folder.png b/data/imgs/folder.png Binary files differnew file mode 100644 index 0000000..9c0db10 --- /dev/null +++ b/data/imgs/folder.png diff --git a/data/imgs/list.png b/data/imgs/list.png Binary files differnew file mode 100644 index 0000000..14c389d --- /dev/null +++ b/data/imgs/list.png diff --git a/data/imgs/logo_64.png b/data/imgs/logo_64.png Binary files differnew file mode 100644 index 0000000..78916bf --- /dev/null +++ b/data/imgs/logo_64.png diff --git a/data/imgs/nl.png b/data/imgs/nl.png Binary files differnew file mode 100644 index 0000000..505b9ce --- /dev/null +++ b/data/imgs/nl.png diff --git a/data/imgs/search.png b/data/imgs/search.png Binary files differnew file mode 100644 index 0000000..0a696b2 --- /dev/null +++ b/data/imgs/search.png diff --git a/data/marketing/cover_image.png b/data/marketing/cover_image.png Binary files differnew file mode 100644 index 0000000..88e1baf --- /dev/null +++ b/data/marketing/cover_image.png diff --git a/data/marketing/logo_32.png b/data/marketing/logo_32.png Binary files differnew file mode 100644 index 0000000..89fa0dc --- /dev/null +++ b/data/marketing/logo_32.png diff --git a/data/marketing/logo_512.ico b/data/marketing/logo_512.ico Binary files differnew file mode 100644 index 0000000..c014524 --- /dev/null +++ b/data/marketing/logo_512.ico diff --git a/data/marketing/logo_512.png b/data/marketing/logo_512.png Binary files differnew file mode 100644 index 0000000..2de1f36 --- /dev/null +++ b/data/marketing/logo_512.png diff --git a/data/marketing/logo_64.png b/data/marketing/logo_64.png Binary files differnew file mode 100644 index 0000000..78916bf --- /dev/null +++ b/data/marketing/logo_64.png diff --git a/data/marketing/main.png b/data/marketing/main.png Binary files differnew file mode 100644 index 0000000..b7628e6 --- /dev/null +++ b/data/marketing/main.png diff --git a/data/marketing/main2.png b/data/marketing/main2.png Binary files differnew file mode 100644 index 0000000..b5fd1b0 --- /dev/null +++ b/data/marketing/main2.png diff --git a/data/marketing/settings1.png b/data/marketing/settings1.png Binary files differnew file mode 100644 index 0000000..92398e5 --- /dev/null +++ b/data/marketing/settings1.png diff --git a/data/marketing/settings2.png b/data/marketing/settings2.png Binary files differnew file mode 100644 index 0000000..ecfb674 --- /dev/null +++ b/data/marketing/settings2.png diff --git a/data/marketing/text-search_banner.png b/data/marketing/text-search_banner.png Binary files differnew file mode 100644 index 0000000..b78de48 --- /dev/null +++ b/data/marketing/text-search_banner.png diff --git a/data/translations/en-English.mo b/data/translations/en-English.mo Binary files differnew file mode 100644 index 0000000..8f5ea46 --- /dev/null +++ b/data/translations/en-English.mo diff --git a/data/translations/nl-Dutch.mo b/data/translations/nl-Dutch.mo Binary files differnew file mode 100644 index 0000000..005e775 --- /dev/null +++ b/data/translations/nl-Dutch.mo diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..d615539 --- /dev/null +++ b/install.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +######################################################################## +######################################################################## +if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + +if [ "$EUID" -ne 0 ] + then echo "Please run this script as root." + exit +fi + +echo "Checking if dependencies are installed.." +if [ $(dpkg-query -W -f='${Status}' libglu1-mesa-dev 2>/dev/null | grep -c "ok installed") -eq 0 ]; +then + apt-get install libglu1-mesa-dev; +fi + + +if [ $(dpkg-query -W -f='${Status}' libgl1-mesa-dev 2>/dev/null | grep -c "ok installed") -eq 0 ]; +then + apt-get install libgl1-mesa-dev; +fi +echo "Dependencies are installed" + +echo "Compiling program.." +cd src + +ld -r -b binary -o ../bin/data.o \ +../data/imgs/en.png \ +../data/imgs/error.png \ +../data/imgs/folder.png \ +../data/imgs/nl.png \ +../data/imgs/search.png \ +../data/imgs/logo_64.png \ +../data/fonts/mono.ttf \ +../data/translations/en-English.mo \ +../data/translations/nl-Dutch.mo \ + +gcc -Wall -O3 -m64 -Wno-unused-label -Wno-unused-variable text_search.c ../bin/data.o -o ../bin/text-search -lX11 -lGL -lGLU -lXrandr -lm -lpthread -ldl + +rm -f ../bin/data.o + +echo "Done compiling program" + +cp --remove-destination ../bin/text-search /usr/local/bin/text-search + +cd ../ + +######################################################################## +######################################################################## +elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then + +windres misc/icon.rc -O coff -o misc/icon.res + +echo "Compiling program.." +cd src + +ld -r -b binary -o ../bin/data.o \ +../data/imgs/en.png \ +../data/imgs/error.png \ +../data/imgs/folder.png \ +../data/imgs/nl.png \ +../data/imgs/search.png \ +../data/imgs/logo_64.png \ +../data/fonts/mono.ttf \ +../data/translations/en-English.mo \ +../data/translations/nl-Dutch.mo \ + +x86_64-w64-mingw32-gcc -Wall -m64 -O3 -Wno-unused-label -Wno-unused-variable text_search.c ../bin/data.o -o ../bin/text-search.exe ../misc/icon.res -lopengl32 -lkernel32 -lglu32 -lgdi32 -lcomdlg32 -lgdiplus -lole32 -lshlwapi + +rm -f ../bin/data.o + +echo "Done compiling program, text-search.exe is located in 'C:\Manually installed'" + +cp --remove-destination "../bin/text-search.exe" "C:\Manually installed programs\text-search.exe" + +cd ../ + +######################################################################## +######################################################################## +elif [ "$(uname)" == "Darwin" ]; then + echo "OSX Platform not supported" +######################################################################## +######################################################################## +elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then + echo "32bit Windows versions not supported" +fi
\ No newline at end of file diff --git a/misc/icon.rc b/misc/icon.rc new file mode 100644 index 0000000..85b2f1d --- /dev/null +++ b/misc/icon.rc @@ -0,0 +1,28 @@ +#include <windows.h> +#include <wingdi.h> +#include <winuser.h> + +RC_LOGO ICON "logo_512.ico" +1 VERSIONINFO +FILEVERSION 0,1,0,0 +PRODUCTVERSION 0,1,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904E4" + BEGIN + VALUE "CompanyName", "Aldrik Ramaekers" + VALUE "FileDescription", "Text-search" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "Text-search" + VALUE "LegalCopyright", "Aldrik Ramaekers" + VALUE "OriginalFilename", "Text-search.exe" + VALUE "ProductName", "Text-search" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1252 + END +END diff --git a/misc/icon.res b/misc/icon.res Binary files differnew file mode 100644 index 0000000..ef35332 --- /dev/null +++ b/misc/icon.res diff --git a/misc/logo_512.ico b/misc/logo_512.ico Binary files differnew file mode 100644 index 0000000..c014524 --- /dev/null +++ b/misc/logo_512.ico diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..2c8127a --- /dev/null +++ b/src/array.c @@ -0,0 +1,204 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +array array_create(u64 entry_size) +{ + array new_array; + new_array.length = 0; + new_array.reserved_length = 0; + new_array.entry_size = entry_size; + new_array.data = 0; + new_array.reserve_jump = 1; + new_array.mutex = mutex_create_recursive(); + + return new_array; +} + +int array_push(array *array, void *data) +{ + assert(array); + assert(data); + assert(array->reserve_jump >= 1); + + mutex_lock(&array->mutex); + array->length++; + + if (!array->data) + { + array->data = mem_alloc(array->entry_size * array->reserve_jump); + array->reserved_length = array->reserve_jump; + } + + if (array->reserved_length < array->length) + { + array->reserved_length += array->reserve_jump; + array->data = mem_realloc(array->data, (array->reserved_length*array->entry_size)); + } + + memcpy(array->data + ((array->length-1) * array->entry_size), + data, array->entry_size); + + s32 result = array->length -1; + mutex_unlock(&array->mutex); + return result; +} + +int array_push_size(array *array, void *data, s32 data_size) +{ + assert(array); + assert(data); + assert(array->reserve_jump >= 1); + + mutex_lock(&array->mutex); + array->length++; + + if (!array->data) + { + array->data = mem_alloc(array->entry_size * array->reserve_jump); + array->reserved_length = array->reserve_jump; + } + + if (array->reserved_length < array->length) + { + array->reserved_length += array->reserve_jump; + array->data = mem_realloc(array->data, (array->reserved_length*array->entry_size)); + } + + memcpy(array->data + ((array->length-1) * array->entry_size), + data, data_size); + + // fill remaining space with 0 + if (array->entry_size > data_size) + { + s32 remaining = array->entry_size - data_size; + memset(array->data + ((array->length-1) * array->entry_size) + data_size, + 0, remaining); + } + + s32 result = array->length -1; + mutex_unlock(&array->mutex); + return result; +} + +void array_reserve(array *array, u32 reserve_count) +{ + assert(array); + + mutex_lock(&array->mutex); + u32 reserved_count = array->reserved_length - array->length; + reserve_count -= reserved_count; + + if (reserve_count > 0) + { + array->reserved_length += reserve_count; + + if (array->data) + array->data = mem_realloc(array->data, (array->reserved_length*array->entry_size)); + else + array->data = mem_alloc(array->reserved_length*array->entry_size); + } + mutex_unlock(&array->mutex); +} + +void array_remove_at(array *array, u32 at) +{ + assert(array); + assert(at >= 0); + assert(at < array->length); + + mutex_lock(&array->mutex); + if (array->length > 1) + { + int offset = at * array->entry_size; + int size = (array->length - at - 1) * array->entry_size; + memcpy(array->data + offset, + array->data + offset + array->entry_size, + size); + + //array->data = realloc(array->data, array->length * array->entry_size); + } + + array->length--; + mutex_unlock(&array->mutex); +} + +void array_remove(array *array, void *ptr) +{ + mutex_lock(&array->mutex); + int offset = ptr - array->data; + int at = offset / array->entry_size; + array_remove_at(array, at); + mutex_unlock(&array->mutex); +} + +void array_remove_by(array *array, void *data) +{ + assert(array); + + mutex_lock(&array->mutex); + for (int i = 0; i < array->length; i++) + { + void *d = array_at(array, i); + if (memcmp(d, data, array->entry_size) == 0) + { + array_remove_at(array, i); + return; + } + } + mutex_unlock(&array->mutex); +} + +void *array_at(array *array, u32 at) +{ + mutex_lock(&array->mutex); + assert(array); + assert(at >= 0); + assert(at < array->length); + + void *result = array->data + (at * array->entry_size); + mutex_unlock(&array->mutex); + return result; +} + +void array_destroy(array *array) +{ + assert(array); + mem_free(array->data); + mutex_destroy(&array->mutex); +} + +void array_swap(array *array, u32 swap1, u32 swap2) +{ + assert(array); + assert(swap2 >= 0); + assert(swap2 < array->length); + if (swap1 == swap2) return; + + void *swap1_at = array_at(array, swap1); + void *swap2_at = array_at(array, swap2); + + mutex_lock(&array->mutex); + char swap1_buffer[array->entry_size]; + memcpy(swap1_buffer, swap1_at, array->entry_size); + memcpy(swap1_at, swap2_at, array->entry_size); + memcpy(swap2_at, swap1_buffer, array->entry_size); + mutex_unlock(&array->mutex); +} + +array array_copy(array *arr) +{ + array new_array; + new_array.length = arr->length; + new_array.reserved_length = arr->reserved_length; + new_array.entry_size = arr->entry_size; + new_array.data = mem_alloc(new_array.entry_size*new_array.reserved_length); + new_array.mutex = mutex_create(); + + mutex_lock(&arr->mutex); + memcpy(new_array.data, arr->data, new_array.entry_size*new_array.reserved_length); + mutex_unlock(&arr->mutex); + return new_array; +}
\ No newline at end of file diff --git a/src/array.h b/src/array.h new file mode 100644 index 0000000..f18f780 --- /dev/null +++ b/src/array.h @@ -0,0 +1,32 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_ARRAY +#define INCLUDE_ARRAY + +typedef struct t_array +{ + u32 length; + u32 reserved_length; + u64 entry_size; + u16 reserve_jump; + void *data; + mutex mutex; +} array; + +array array_create(u64 entry_size); +int array_push(array *array, void *data); +int array_push_size(array *array, void *data, s32 data_size); +void array_remove_at(array *array, u32 at); +void array_remove(array *array, void *ptr); +void array_remove_by(array *array, void *data); +void *array_at(array *array, u32 at); +void array_destroy(array *array); +void array_swap(array *array, u32 swap1, u32 swap2); +void array_reserve(array *array, u32 reserve_count); +array array_copy(array *array); + +#endif
\ No newline at end of file diff --git a/src/assets.c b/src/assets.c new file mode 100644 index 0000000..37334dd --- /dev/null +++ b/src/assets.c @@ -0,0 +1,313 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +void assets_create() +{ + assets asset_collection; + asset_collection.images = array_create(sizeof(image)); + asset_collection.fonts = array_create(sizeof(font)); + + array_reserve(&asset_collection.images, ASSET_IMAGE_COUNT); + array_reserve(&asset_collection.fonts, ASSET_FONT_COUNT); + + asset_collection.queue.queue = array_create(sizeof(asset_task)); + asset_collection.post_process_queue = array_create(sizeof(asset_task)); + + array_reserve(&asset_collection.queue.queue, ASSET_QUEUE_COUNT); + array_reserve(&asset_collection.post_process_queue, ASSET_QUEUE_COUNT); + + asset_mutex = mutex_create(); + asset_collection.valid = true; + asset_collection.done_loading_assets = false; + + global_asset_collection = asset_collection; +} + +inline static bool is_big_endian() +{ + volatile uint32_t i=0x01234567; + // return 1 for big endian, 0 for little endian. + return !((*((uint8_t*)(&i))) == 0x67); +} + +void assets_do_post_process() +{ + mutex_lock(&asset_mutex); + + for (int i = 0; i < global_asset_collection.post_process_queue.length; i++) + { + asset_task *task = array_at(&global_asset_collection.post_process_queue, i); + + if (task->type == ASSET_IMAGE) + { + if (task->image->data && task->valid) + { + glGenTextures(1, &task->image->textureID); + glBindTexture(GL_TEXTURE_2D, task->image->textureID); + + s32 flag = is_big_endian() ? GL_UNSIGNED_INT_8_8_8_8 : + GL_UNSIGNED_INT_8_8_8_8_REV; + + glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA8, task->image->width, + task->image->height, 0, GL_RGBA, flag, task->image->data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + task->image->loaded = true; + + if (!task->image->keep_in_memory) + stbi_image_free(task->image->data); + } + } + else if (task->type == ASSET_FONT) + { + if (task->valid) + { + for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++) + { + glyph *g = &task->font->glyphs[i]; + + glGenTextures(1, &g->textureID); + glBindTexture(GL_TEXTURE_2D, g->textureID); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, g->width,g->height, + 0, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap ); + + mem_free(g->bitmap); + } + + task->font->loaded = true; + } + } + + array_remove_at(&global_asset_collection.post_process_queue, i); + } + + mutex_unlock(&asset_mutex); +} + +bool assets_queue_worker_load_image(image *image) +{ + set_active_directory(binary_path); + + image->data = stbi_load_from_memory(image->start_addr, + image->end_addr - image->start_addr, + &image->width, + &image->height, + &image->channels, + STBI_rgb_alpha); + + return !(image->data == 0); +} + +bool assets_queue_worker_load_font(font *font) +{ + unsigned char *ttf_buffer = (unsigned char*)font->start_addr; + + stbtt_fontinfo info; + if (!stbtt_InitFont(&info, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0))) + { + return false; + } + float scale = stbtt_ScaleForPixelHeight(&info, font->size); + + for (s32 i = TEXT_CHARSET_START; i < TEXT_CHARSET_END; i++) + { + s32 w, h, xoff, yoff; + + glyph new_glyph; + new_glyph.bitmap = stbtt_GetCodepointBitmap(&info, 0, scale, i, &w, &h, &xoff, &yoff); + new_glyph.width = w; + new_glyph.height = h; + new_glyph.xoff = xoff; + new_glyph.yoff = yoff; + + if (i == 'M') font->px_h = -yoff; + if (i == ' ') new_glyph.xoff = font->size/3; + + font->glyphs[i-TEXT_CHARSET_START] = new_glyph; + } + + font->info = info; + font->scale = scale; + + return true; +} + +void *assets_queue_worker() +{ + while (global_asset_collection.valid && !global_asset_collection.done_loading_assets) + { + if (mutex_trylock(&asset_mutex)) + { + int queue_length = global_asset_collection.queue.queue.length; + if (!queue_length) + { + mutex_unlock(&asset_mutex); + continue; + } + + asset_task *task = array_at(&global_asset_collection.queue.queue, 0); + asset_task buf = *task; + array_remove_at(&global_asset_collection.queue.queue, 0); + mutex_unlock(&asset_mutex); + + // load here + if (buf.type == ASSET_IMAGE) + { + bool result = assets_queue_worker_load_image(buf.image); + buf.valid = result; + } + else if (buf.type == ASSET_FONT) + { + bool result = assets_queue_worker_load_font(buf.font); + buf.valid = result; + } + + mutex_lock(&asset_mutex); + + assert(global_asset_collection.post_process_queue.reserved_length > + global_asset_collection.post_process_queue.length); + + array_push(&global_asset_collection.post_process_queue, &buf); + mutex_unlock(&asset_mutex); + } + + // 3 ms + thread_sleep(3000); + } + + return 0; +} + +image *assets_load_image(u8 *start_addr, u8 *end_addr, bool keep_in_memory) +{ + // check if image is already loaded or loading + for (int i = 0; i < global_asset_collection.images.length; i++) + { + image *img_at = array_at(&global_asset_collection.images, i); + + if (start_addr == img_at->start_addr) + { + // image is already loaded/loading + img_at->references++; + return img_at; + } + } + + image new_image; + new_image.loaded = false; + new_image.start_addr = start_addr; + new_image.end_addr = end_addr; + new_image.references = 1; + new_image.keep_in_memory = keep_in_memory; + + // NOTE(Aldrik): we should never realloc the image array because pointers will be + // invalidated. + assert(global_asset_collection.images.reserved_length > global_asset_collection.images.length); + + int index = array_push(&global_asset_collection.images, &new_image); + + asset_task task; + task.type = ASSET_IMAGE; + task.image = array_at(&global_asset_collection.images, index); + + mutex_lock(&asset_mutex); + array_push(&global_asset_collection.queue.queue, &task); + mutex_unlock(&asset_mutex); + + return task.image; +} + +void assets_destroy_image(image *image_to_destroy) +{ + if (image_to_destroy->references == 1) + { + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &image_to_destroy->textureID); + + if (image_to_destroy->keep_in_memory) + stbi_image_free(image_to_destroy->data); + + //array_remove(&global_asset_collection.images, image_at); + } + else + { + image_to_destroy->references--; + } +} + +font *assets_load_font(u8 *start_addr, u8 *end_addr, s16 size) +{ + //assert(!(size % 4)); + for (int i = 0; i < global_asset_collection.fonts.length; i++) + { + font *font_at = array_at(&global_asset_collection.fonts, i); + + if (start_addr == font_at->start_addr && font_at->size == size) + { + // font is already loaded/loading + font_at->references++; + return font_at; + } + } + + font new_font; + new_font.loaded = false; + new_font.start_addr = start_addr; + new_font.end_addr = end_addr; + new_font.size = size; + new_font.references = 1; + + // NOTE(Aldrik): we should never realloc the font array because pointers will be + // invalidated. + assert(global_asset_collection.fonts.reserved_length > global_asset_collection.fonts.length); + + int index = array_push(&global_asset_collection.fonts, &new_font); + + asset_task task; + task.type = ASSET_FONT; + task.font = array_at(&global_asset_collection.fonts, index); + + mutex_lock(&asset_mutex); + array_push(&global_asset_collection.queue.queue, &task); + mutex_unlock(&asset_mutex); + + return task.font; +} + +void assets_destroy_font(font *font_to_destroy) +{ + if (font_to_destroy->references == 1) + { + //glBindTexture(GL_TEXTURE_2D, 0); + //glDeleteTextures(1, font_to_destroy->textureIDs); + } + else + { + font_to_destroy->references--; + } +} + +void assets_destroy() +{ + global_asset_collection.valid = false; + global_asset_collection.done_loading_assets = false; + + array_destroy(&global_asset_collection.images); + array_destroy(&global_asset_collection.fonts); + + array_destroy(&global_asset_collection.queue.queue); + array_destroy(&global_asset_collection.post_process_queue); + + mem_free(binary_path); + + mutex_destroy(&asset_mutex); +} diff --git a/src/assets.h b/src/assets.h new file mode 100644 index 0000000..3cfac52 --- /dev/null +++ b/src/assets.h @@ -0,0 +1,149 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_ASSETS +#define INCLUDE_ASSETS + +#ifndef ASSET_IMAGE_COUNT +#define ASSET_IMAGE_COUNT 10 +#endif + +#ifndef ASSET_FONT_COUNT +#define ASSET_FONT_COUNT 10 +#endif + +#ifndef ASSET_QUEUE_COUNT +#define ASSET_QUEUE_COUNT 10 +#endif + +// binary blobs +extern u8 _binary____data_imgs_en_png_start[]; +extern u8 _binary____data_imgs_en_png_end[]; + +extern u8 _binary____data_imgs_error_png_start[]; +extern u8 _binary____data_imgs_error_png_end[]; + +extern u8 _binary____data_imgs_folder_png_start[]; +extern u8 _binary____data_imgs_folder_png_end[]; + +extern u8 _binary____data_imgs_nl_png_start[]; +extern u8 _binary____data_imgs_nl_png_end[]; + +extern u8 _binary____data_imgs_search_png_start[]; +extern u8 _binary____data_imgs_search_png_end[]; + +extern u8 _binary____data_imgs_logo_64_png_start[]; +extern u8 _binary____data_imgs_logo_64_png_end[]; + +extern u8 _binary____data_imgs_logo_512_png_start[]; +extern u8 _binary____data_imgs_logo_512_png_end[]; + +extern u8 _binary____data_fonts_mono_ttf_start[]; +extern u8 _binary____data_fonts_mono_ttf_end[]; + +extern u8 _binary____data_translations_en_English_mo_start[]; +extern u8 _binary____data_translations_en_English_mo_end[]; + +extern u8 _binary____data_translations_nl_Dutch_mo_start[]; +extern u8 _binary____data_translations_nl_Dutch_mo_end[]; + +extern u8 _binary____data_imgs_list_png_start[]; +extern u8 _binary____data_imgs_list_png_end[]; + +extern u8 _binary____data_imgs_delete_png_start[]; +extern u8 _binary____data_imgs_delete_png_end[]; + +extern u8 _binary____data_imgs_exclaim_png_start[]; +extern u8 _binary____data_imgs_exclaim_png_end[]; + + +typedef struct t_image { + u8 *start_addr; + u8 *end_addr; + bool loaded; + bool keep_in_memory; + s32 width; + s32 height; + s32 channels; + void *data; + s16 references; + GLuint textureID; +} image; + +#define TEXT_CHARSET_START 0 +#define TEXT_CHARSET_END 2000 +#define TOTAL_GLYPHS TEXT_CHARSET_END-TEXT_CHARSET_START + +typedef struct t_glyph +{ + s32 width; + s32 height; + s32 xoff; + s32 yoff; + void *bitmap; + GLuint textureID; +} glyph; + +typedef struct t_font +{ + u8 *start_addr; + u8 *end_addr; + bool loaded; + s16 references; + s16 size; + s32 px_h; + float32 scale; + stbtt_fontinfo info; + glyph glyphs[TOTAL_GLYPHS]; +} font; + +typedef enum t_asset_task_type +{ + ASSET_IMAGE, + ASSET_FONT, +} asset_task_type; + +typedef struct t_asset_task +{ + s8 type; + bool valid; + union { + image *image; + font *font; + }; +} asset_task; + +typedef struct t_asset_queue { + array queue; +} asset_queue; + +typedef struct t_assets { + array images; + array fonts; + asset_queue queue; + array post_process_queue; + bool valid; + bool done_loading_assets; +} assets; + +char *binary_path; + +mutex asset_mutex; +assets global_asset_collection; + +void assets_create(); +void assets_destroy(); + +void assets_do_post_process(); +void *assets_queue_worker(); + +image *assets_load_image(u8 *start_addr, u8 *end_addr, bool keep_in_memory); +void assets_destroy_image(image *image); + +font *assets_load_font(u8 *start_addr, u8 *end_addr, s16 size); +void assets_destroy_font(font *font); + +#endif
\ No newline at end of file diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 0000000..5bde9f9 --- /dev/null +++ b/src/camera.c @@ -0,0 +1,22 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +void camera_apply_transformations(platform_window *window, camera *camera) +{ + s32 x = (window->width/2)+(camera->x); + s32 y = (window->height/2)+(camera->y); + glTranslatef(x, y, 0.0f); + glRotatef(camera->rotation, 0.0f, 0.0f, 1.0f); + glTranslatef(-x, -y, 0.0f); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho(camera->x, window->width+camera->x, + window->height+camera->y, camera->y, -100, 100); + + glMatrixMode(GL_MODELVIEW); +}
\ No newline at end of file diff --git a/src/camera.h b/src/camera.h new file mode 100644 index 0000000..c6e4093 --- /dev/null +++ b/src/camera.h @@ -0,0 +1,19 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_CAMERA +#define INCLUDE_CAMERA + +typedef struct t_camera +{ + float32 x; + float32 y; + float32 rotation; +} camera; + +void camera_apply_transformations(platform_window *window, camera *camera); + +#endif
\ No newline at end of file diff --git a/src/command_line.c b/src/command_line.c new file mode 100644 index 0000000..b1718f9 --- /dev/null +++ b/src/command_line.c @@ -0,0 +1,265 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#if 0 +void find_text_in_files(search_result *search_result); +s32 prepare_search_directory_path(char *path, s32 len); +search_result *create_empty_search_result(); +void do_search(); +bool export_results(search_result *result); + +static void print_license_message() +{ + time_t t = time(0); + struct tm *now = localtime(&t); + + printf("BSD 2-Clause License\n" + "\n" + "Copyright (c) %d, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com\n" + "All rights reserved.\n" + "\n" + "Redistribution and use in source and binary forms, with or without\n" + "modification, are permitted provided that the following conditions are met:\n" + "\n" + "1. Redistributions of source code must retain the above copyright notice, this\n" + "list of conditions and the following disclaimer.\n" + "\n" + "2. Redistributions in binary form must reproduce the above copyright notice,\n" + "this list of conditions and the following disclaimer in the documentation\n" + "and/or other materials provided with the distribution.\n" + "\n" + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n" + "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n" + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n" + "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n" + "FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n" + "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n" + "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n" + "OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n", now->tm_year+1900); + + printf("The following parts of the Software are separately licensed:\n" + "- The Liberation font family is licensed under the SIL Open Font License" "(version 2 onwards)\n\n"); +} + +static void print_help_message() +{ +#define explain_required_argument(c,e) printf(" %-18s%-18s%s\n", c, "REQUIRED", e); +#define explain_other_argument(c,e) printf(" %-18s%-18s%s\n", c, " ", e); +#define explain_optional_argument(c,d,e) printf(" %-18s%-18s%s\n", c, d, e); +#define DEFAULT(d) "default=\""d"\"" + + printf("Usage: text-search [OPTION] ... [OPTION] ...\n"); + printf("Example: text-search " + "--directory \"/home/john/Documents\" --text \"homework\" --recursive 0\n"); + printf("Text-search, search for files and text within files.\n"); + printf("Matches will be printed to console in format: [path]:[line]:[filter]\n\n"); + + printf("Available arguments:\n"); + explain_required_argument("--directory", "The directory to search"); + explain_optional_argument("--text", DEFAULT("*"), "The text to search for within files, supports wildcards '*' and '?'"); + explain_optional_argument("--filter", DEFAULT("*"), "Used to filter on specific files, supports wildcards '*' and '?'"); + explain_optional_argument("--recursive", DEFAULT("1"), "Recursively search through directories"); + explain_optional_argument("--max-file-size", DEFAULT("0"), "The maximum size, in kb, a file will be searched through for matching text. 0 for no limit"); + explain_optional_argument("--threads", DEFAULT("10"), "The number of threads used for searching, minimum of 1 thread."); + explain_optional_argument("--locale", DEFAULT("en"), "The language errors will be reported in. Available locales are: 'en', 'nl'"); + explain_optional_argument("--export", DEFAULT(""), "Export the results to a file in json format"); + + printf("\nOther arguments:\n"); + explain_other_argument("--help", "Display this help message"); + explain_other_argument("--license", "Display the license"); +} + +static bool is_valid_argument(char *arg) +{ + if (string_equals(arg, "--directory")) return true; + if (string_equals(arg, "--text")) return true; + if (string_equals(arg, "--filter")) return true; + if (string_equals(arg, "--recursive")) return true; + if (string_equals(arg, "--max-file-size")) return true; + if (string_equals(arg, "--threads")) return true; + if (string_equals(arg, "--locale")) return true; + if (string_equals(arg, "--export")) return true; + + return false; +} +#endif +void handle_command_line_arguments(int argc, char **argv) +{ +#if 0 + load_available_localizations(); + set_locale("en"); + + s32 current_arg_index = 1; + bool is_help_request = string_equals(argv[current_arg_index], "--help"); + bool is_license_request = string_equals(argv[current_arg_index], "--license"); + + if (is_help_request) + { + print_help_message(); + return; + } + else if (is_license_request) + { + print_license_message(); + return; + } + + char directory[MAX_INPUT_LENGTH]; + string_copyn(directory, "", MAX_INPUT_LENGTH); + + char text[MAX_INPUT_LENGTH]; + string_copyn(text, "*", MAX_INPUT_LENGTH); + + char filter[MAX_INPUT_LENGTH]; + string_copyn(filter, "*", MAX_INPUT_LENGTH); + + bool recursive = true; + s32 max_file_size = 0; + s32 threads = 10; + + char locale[MAX_INPUT_LENGTH]; + string_copyn(locale, "en", MAX_INPUT_LENGTH); + + char export_path[MAX_INPUT_LENGTH]; + string_copyn(export_path, "", MAX_INPUT_LENGTH); + + bool expect_argument_name = true; + for (s32 i = current_arg_index; i < argc; i++) + { + if (expect_argument_name && !is_valid_argument(argv[i])) + { + printf("%s: %s\n", localize("invalid_argument"), argv[i]); + } + + if (!expect_argument_name) + { + if (string_equals(argv[i-1], "--directory")) + { + string_copyn(directory, argv[i], MAX_INPUT_LENGTH); + } + if (string_equals(argv[i-1], "--text")) + { + string_copyn(text, argv[i], MAX_INPUT_LENGTH); + } + if (string_equals(argv[i-1], "--filter")) + { + string_copyn(filter, argv[i], MAX_INPUT_LENGTH); + } + if (string_equals(argv[i-1], "--recursive")) + { + recursive = string_to_u32(argv[i]); + } + if (string_equals(argv[i-1], "--max-file-size")) + { + max_file_size = string_to_u32(argv[i]); + } + if (string_equals(argv[i-1], "--threads")) + { + threads = string_to_u32(argv[i]); + } + if (string_equals(argv[i-1], "--locale")) + { + string_copyn(locale, argv[i], MAX_INPUT_LENGTH); + } + if (string_equals(argv[i-1], "--export")) + { + string_copyn(export_path, argv[i], MAX_INPUT_LENGTH); + } + } + + expect_argument_name = !expect_argument_name; + } + + // input validation + if (!set_locale(locale)) + { + printf(localize("warning_locale_not_available"), locale); + printf("\n"); + } + + if (string_equals(directory, "")) + { + printf("%s", localize("error_directory_not_specified")); + printf("\n"); + return; + } + + if (!platform_directory_exists(directory)) + { + printf(localize("error_directory_not_found"), directory); + printf("\n"); + return; + } + + if (string_equals(text, "")) + { + printf("%s", localize("error_text_argument_empty")); + printf("\n"); + return; + } + + if (string_equals(filter, "")) + { + printf("%s", localize("error_filter_argument_empty")); + printf("\n"); + return; + } + + if (threads < 1) + { + printf("%s", localize("error_threads_too_low")); + printf("\n"); + return; + } + + if (!string_equals(export_path, "")) + { + char dir_buffer[MAX_INPUT_LENGTH]; + get_directory_from_path(dir_buffer, export_path); + + if (!platform_directory_exists(dir_buffer)) + { + printf(localize("error_invalid_export_path"), dir_buffer); + printf("\n"); + return; + } + } + + search_result *result = create_empty_search_result(); + string_copyn(result->directory_to_search, directory, MAX_INPUT_LENGTH); + string_copyn(result->file_filter, filter, MAX_INPUT_LENGTH); + string_copyn(result->text_to_find, text, MAX_INPUT_LENGTH); + string_copyn(result->export_path, export_path, MAX_INPUT_LENGTH); + result->max_thread_count = threads; + result->max_file_size = max_file_size; + result->is_recursive = recursive; + result->is_command_line_search = true; + + + // begin search (code below is equal to code in text_search.c) + result->walking_file_system = true; + result->done_finding_matches = false; + + result->search_result_source_dir_len = strlen(result->directory_to_search); + result->search_result_source_dir_len = prepare_search_directory_path(result->directory_to_search, + result->search_result_source_dir_len); + result->start_time = platform_get_time(TIME_FULL, TIME_US); + + platform_list_files(&result->files, result->directory_to_search, result->file_filter, result->is_recursive, &result->mem_bucket, + &result->cancel_search, + &result->done_finding_files); + find_text_in_files(result); + + while(!result->threads_closed) { thread_sleep(1000); } + + if (!string_equals(export_path, "")) + { + export_results(result); + } +#endif +}
\ No newline at end of file diff --git a/src/command_line.h b/src/command_line.h new file mode 100644 index 0000000..74daa8d --- /dev/null +++ b/src/command_line.h @@ -0,0 +1,12 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_COMMAND_LINE +#define INCLUDE_COMMAND_LINE + +void handle_command_line_arguments(int argc, char **argv); + +#endif
\ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..a1a7611 --- /dev/null +++ b/src/config.h @@ -0,0 +1,18 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_CONFIG +#define INCLUDE_CONFIG + +#define TARGET_FRAMERATE 1000/24.0 + +#define SCROLL_SPEED 50 +#define FILE_RESERVE_COUNT 500 +#define ERROR_RESERVE_COUNT 10 +#define MAX_ERROR_MESSAGE_LENGTH 120 +#define MAX_STATUS_TEXT_LENGTH 120 + +#endif
\ No newline at end of file diff --git a/src/external/LooplessSizeMove.c b/src/external/LooplessSizeMove.c new file mode 100644 index 0000000..1843cb9 --- /dev/null +++ b/src/external/LooplessSizeMove.c @@ -0,0 +1,864 @@ +/* + LooplessSizeMove.c + Implements functions for modal-less window resizing and movement in Windows + + Author: Nathaniel J Fries + + The author asserts no copyright, this work is released into the public domain. +*/ + + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#ifdef TIME_LOOP +#include <stdio.h> +#endif /* TIME_LOOP */ + +/* fills the MINMAXINFO structure pointed to by the second argument with + default MIMAX values, then sends WM_GETMINMAXINFO to allow the + user's Window Procedure to modify it. +*/ +void GetMinMaxInfo(HWND hwnd, PMINMAXINFO info); +/* begins the loopless resize/move process */ +LRESULT PrepareSizeMove(HWND hwnd, WPARAM action, DWORD dwPos); +/* stops the loopless resize/move process. + if cancel is TRUE, restores window size and position to + what they were before resizing/moving. +*/ +void StopSizing(BOOL cancel); +/* see LooplessSizeMove.h */ +LRESULT CALLBACK LSMProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +BOOLEAN SizingCheck(const MSG *lpmsg); + +#define GetWindowLongAW(hwnd, lp)\ +(IsWindowUnicode(hwnd)) ? \ +(GetWindowLongW(hwnd, lp)) : \ +(GetWindowLongA(hwnd, lp)) +#define SendMessageAW(hwnd, msg, wParam, lParam)\ +(IsWindowUnicode(hwnd)) ? \ +(SendMessageW(hwnd, msg, wParam, lParam)) : \ +(SendMessageA(hwnd, msg, wParam, lParam)) +#define PostMessageAW(hwnd, msg, wParam, lParam)\ +(IsWindowUnicode(hwnd)) ? \ +(PostMessageW(hwnd, msg, wParam, lParam)) : \ +(PostMessageA(hwnd, msg, wParam, lParam)) +#define DefWindowProcAW(hwnd, msg, wParam, lParam)\ +(IsWindowUnicode(hwnd)) ? \ +(DefWindowProcW(hwnd, msg, wParam, lParam)) : \ +(DefWindowProcA(hwnd, msg, wParam, lParam)) + +#define RECTWIDTH(r) ((r).right - (r).left) +#define RECTHEIGHT(r) ((r).bottom - (r).top) + +#define LSM_LEFT 0x01 +#define LSM_TOP 0x02 +#define LSM_RIGHT 0x04 +#define LSM_BOTTOM 0x08 +#define LSM_CAPTION 0x00 +#define LSM_NOGRAB 0xF0 + +#define LSM_SHAKE_MINTIME 20 /* minimum time between bothering */ +#define LSM_SHAKE_MAXTIME 1000 /* maximum time between movements for "shake" effect */ +#define LSM_SHAKE_STATE_MAXIMIZED 1 +#define LSM_SNAP_HELPER_CLASS "LSM_SNAP_HELPER" +/* + information required to reverse a "shake" action. +*/ +typedef struct _SHAKERESTORENODE +{ + HWND hwnd; + WINDOWPLACEMENT place; + struct _SHAKERESTORENODE *next; +} SHAKERESTORENODE, *PSHAKERESTORENODE; +typedef struct +{ + DWORD dwPrevTime; + WORD wDir; + WORD wCount; + SHAKERESTORENODE *restoreList; +} SHAKEDATA; +typedef BOOL (*SHAKEFOREACHFN)(PSHAKERESTORENODE, LPARAM); + +typedef struct +{ + HWND helperWindow; + WORD isSnapped; + WORD snapType; /* LSM_TOP, LSM_LEFT, LSM_RIGHT */ + RECT rcWork; + RECT rcRestore; +} SNAPDATA; + +/* holds all data that needs to be held on to for resizing */ +typedef struct +{ + HWND hwnd; + MINMAXINFO minmax; + RECT rcWin; + RECT rcOrig; + POINT ptCapture; + LONG grab; + SHAKEDATA shake; + SNAPDATA snap; +} SIZEMOVEDATA; + +DWORD dwSizeMoveTlsIndex = 0; +#define LSMTlsCheck() if(dwSizeMoveTlsIndex == 0){ dwSizeMoveTlsIndex = TlsAlloc(); } +#define LSMSet(data) TlsSetValue(dwSizeMoveTlsIndex, data) +#define LSMGet() TlsGetValue(dwSizeMoveTlsIndex) + +void GetMinMaxInfo(HWND hwnd, PMINMAXINFO info) +{ + RECT rc; + LONG style = GetWindowLongAW(hwnd, GWL_STYLE); + LONG altStyle = ((style & WS_CAPTION) == WS_CAPTION)? + (style & ~WS_BORDER):(style); + + /* calculate the default values in case WindowProc does not respond */ + GetClientRect(GetParent(hwnd), &rc); + AdjustWindowRectEx(&rc, altStyle, ((style & WS_POPUP) && GetMenu(hwnd)), + GetWindowLongAW(hwnd, GWL_EXSTYLE)); + info->ptMaxPosition.x = rc.left; + info->ptMaxPosition.y = rc.top; + info->ptMaxSize.x = rc.right - rc.left; + info->ptMaxSize.y = rc.bottom - rc.top; + if(style & WS_CAPTION) + { + info->ptMinTrackSize.x = GetSystemMetrics(SM_CXMINTRACK); + info->ptMaxTrackSize.y = GetSystemMetrics(SM_CYMINTRACK); + } + else + { + /* why not zero? this is what ReactOS and presumably Wine do, + and they're the experts at replicating Windows UI behavior */ + info->ptMinTrackSize.x = info->ptMaxPosition.x * -2; + info->ptMaxTrackSize.y = info->ptMaxPosition.y * -2; + } + info->ptMaxTrackSize.x = GetSystemMetrics(SM_CXMAXTRACK); + info->ptMaxTrackSize.y = GetSystemMetrics(SM_CYMAXTRACK); + + /* ask Window proc to make any changes */ + SendMessageAW(hwnd, WM_GETMINMAXINFO, 0, (LPARAM)info); +} + +BOOL ForEachShakeNodeFree(PSHAKERESTORENODE node, LPARAM ignore) +{ + LocalFree(node); + return TRUE; +} +BOOL ForEachShakeNode(SHAKEDATA *pShake, SHAKEFOREACHFN fn, LPARAM lParam) +{ + SHAKERESTORENODE *next, *curr; + BOOL res = TRUE; + if(pShake->restoreList) + { + curr = pShake->restoreList; + while(res && curr) + { + next = curr->next; + fn(curr, lParam); + curr = next; + } + } + return res; +} + +void SnapCleanup(SIZEMOVEDATA *sizemove) +{ + ShowWindow(sizemove->snap.helperWindow, SW_HIDE); + ZeroMemory(&sizemove->snap.rcWork, sizeof(RECT)); +} + +LRESULT WINAPI SnapHelperWinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_PAINT: + { + SIZEMOVEDATA *sizemove = LSMGet(); + HDC hDC = GetDC(hwnd); + HPEN hPen = CreatePen(PS_INSIDEFRAME, 1, GetSysColor(COLOR_HIGHLIGHT)); + LOGBRUSH blog = { BS_HOLLOW, 0, 0 }; + HBRUSH hBrush = CreateBrushIndirect(&blog); + if(!sizemove || !hDC || !hPen || !hBrush) + break; + + SelectObject(hDC, hPen); + SelectObject(hDC, hBrush); + + Rectangle(hDC, 0, 0, RECTWIDTH(sizemove->snap.rcWork), RECTHEIGHT(sizemove->snap.rcWork)); + + DeleteObject(hBrush); + DeleteObject(hPen); + break; + } + default: + { + break; + } + } + return DefWindowProcA(hwnd, msg, wParam, lParam); +} + +LRESULT PrepareSizeMove(HWND hwnd, WPARAM action, DWORD dwPos) +{ + WINDOWINFO winfo; + SIZEMOVEDATA *sm; + RECT rcClipCursor; + + winfo.cbSize = sizeof(WINDOWINFO); + /* most likely not a valid window */ + if(GetWindowInfo(hwnd, &winfo) == FALSE) + return 0; + /* can't move or resize an invisible window */ + if(!IsWindowVisible(hwnd)) + return 0; + /* can't resize a window without the resizing border */ + if((action & 0xfff0) == SC_MOVE && !(winfo.dwStyle & WS_SIZEBOX)) + return 0; + + /* + if another window on this thread has capture, + it might be using this too... + tell it to clean up before setting the tls value + */ + ReleaseCapture(); + + if(!(sm = LSMGet())) + { + WNDCLASSA wndcls; + HINSTANCE hInstance = GetModuleHandleA(NULL); + sm = LocalAlloc(0, sizeof(SIZEMOVEDATA)); + if(!sm) + { + /* error */ + return 1; + } + if(!LSMSet(sm)) + { + LocalFree(sm); + return 2; + } + /* prevent potential crashes and bad initialization bugs */ + ZeroMemory(sm, sizeof(SIZEMOVEDATA)); + + if(!GetClassInfoA(hInstance, LSM_SNAP_HELPER_CLASS, &wndcls)) + { + ZeroMemory(&wndcls, sizeof(WNDCLASSA)); + wndcls.style = CS_SAVEBITS; + wndcls.hbrBackground = GetSysColorBrush(COLOR_HOTLIGHT); + wndcls.lpszClassName = LSM_SNAP_HELPER_CLASS; + wndcls.lpfnWndProc = SnapHelperWinProc; + wndcls.hInstance = hInstance; + RegisterClassA(&wndcls); + } + /* WS_EX_LAYERED: window is not fully opaque + WS_EX_TRANSPARENT: mouse events pass through layered window + ES_EX_NOACTIVATE: window cannot be activated by user (no loss of mouse capture or keyboard focus) + */ + sm->snap.helperWindow = CreateWindowExA(WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE, + LSM_SNAP_HELPER_CLASS, "", WS_POPUP /* no borders */, 0, 0, 100, 100, NULL, NULL, hInstance, NULL); + if(sm->snap.helperWindow) + { + SetLayeredWindowAttributes(sm->snap.helperWindow, 0, 75, LWA_ALPHA); + } + } + else if(sm->hwnd != hwnd) + { + /* forget shake data, consistent with Windows 7 behavior */ + ForEachShakeNode(&sm->shake, ForEachShakeNodeFree, 0); + SnapCleanup(sm); + sm->shake.restoreList = NULL; + } + sm->grab = action & 0x000f; + sm->hwnd = hwnd; + GetMinMaxInfo(hwnd, &sm->minmax); + sm->rcWin = winfo.rcWindow; + if(winfo.dwStyle & WS_CHILD) + { + /* map points into the parent's coordinate space */ + HWND parent = GetParent(hwnd); + MapWindowPoints(0, parent, (LPPOINT)&sm->rcWin, 2); + GetWindowRect(parent, &rcClipCursor); + MapWindowPoints(parent, HWND_DESKTOP, (LPPOINT)&rcClipCursor, 2); + } + else if(!(winfo.dwExStyle & WS_EX_TOPMOST)) + { + SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcClipCursor, 0); + } + else + { + rcClipCursor.left = rcClipCursor.top = 0; + rcClipCursor.right = GetSystemMetrics(SM_CXSCREEN); + rcClipCursor.bottom = GetSystemMetrics(SM_CYSCREEN); + } + sm->rcOrig = sm->rcWin; + + sm->ptCapture.x = (short)LOWORD(dwPos); + sm->ptCapture.y = (short)HIWORD(dwPos); + //ClipCursor(&rcClipCursor); + + + /* notify WinProc we're beginning, but return instead of looping */ + SendMessageAW(hwnd, WM_ENTERSIZEMOVE, 0, 0); + if(GetCapture() != hwnd) + { + SetCapture(hwnd); + } + return 0; +} + +LRESULT CALLBACK LSMProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LSMTlsCheck(); + switch(msg) + { + case WM_NCLBUTTONDOWN: + { + switch(wParam) + { + case HTLEFT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_LEFT, lParam); + case HTTOPLEFT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_LEFT | LSM_TOP, lParam); + case HTBOTTOMLEFT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_LEFT | LSM_BOTTOM, lParam); + case HTRIGHT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_RIGHT, lParam); + case HTTOPRIGHT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_RIGHT | LSM_TOP, lParam); + case HTBOTTOMRIGHT: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_RIGHT | LSM_BOTTOM, lParam); + case HTTOP: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_TOP, lParam); + case HTBOTTOM: + return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_SIZE | LSM_BOTTOM, lParam); + case HTCAPTION: + //return SendMessageAW(hwnd, WM_SYSCOMMAND, SC_MOVE | LSM_CAPTION, lParam); + default: + break; + } + } + case WM_SYSCOMMAND: + { + switch(wParam & 0xfff0) + { + case SC_SIZE: + /* begin resize 'loop' */ + return PrepareSizeMove(hwnd, wParam, lParam); + default: + break; + } + break; + } + case WM_CAPTURECHANGED: + { + /* nothing we can do; stop resizing & do clean-up */ + SIZEMOVEDATA *sizemove = LSMGet(); + if(sizemove && (HWND)lParam != sizemove->hwnd) + { + SendMessageAW(sizemove->hwnd, WM_EXITSIZEMOVE, 0, 0); + ClipCursor(NULL); + /* no longer unset LSM data: + 1) never really needed to + 2) necessary to hold onto shake data + */ + if(GetForegroundWindow() != sizemove->hwnd) + { + /* forget shake data, consistent with Windows 7 behavior */ + ForEachShakeNode(&sizemove->shake, ForEachShakeNodeFree, 0); + sizemove->shake.restoreList = NULL; + } + sizemove->shake.wCount = 0; + sizemove->shake.wDir = 0; + sizemove->shake.dwPrevTime = 0; + SnapCleanup(sizemove); + + sizemove->grab = LSM_NOGRAB; + } + } + default: + break; + } + return DefWindowProcAW(hwnd, msg, wParam, lParam); +} + +BOOL CALLBACK EnumWindowsShakeMinimize(HWND hwnd, LPARAM lParam) +{ + SIZEMOVEDATA *sizemove = (SIZEMOVEDATA *)(lParam); + if(hwnd != sizemove->hwnd && IsWindowVisible(hwnd) && !IsIconic(hwnd)) + { + SHAKERESTORENODE *pNode = 0; + WINDOWPLACEMENT tPlace; + /* note: there are a few shell-created windows that glitch up graphically if you try to minimize them */ + DWORD dwProcID, dwShellPID; + GetWindowThreadProcessId(hwnd, &dwProcID); + GetWindowThreadProcessId(GetShellWindow(), &dwShellPID); + if(dwProcID == dwShellPID) + { + return TRUE; + } + /* note: not supposed to minimize "parent application" windows. I assume this means windows of + the window's application? + See: http://social.technet.microsoft.com/Forums/windows/en-US/b047378c-2a5a-4661-a871-cec459cb9bdc/aeroshake-not-working?forum=w7itproui + */ + GetWindowThreadProcessId(sizemove->hwnd, &dwShellPID); + if(dwProcID == dwShellPID) + { + return TRUE; + } + + + pNode = LocalAlloc(0, sizeof(SHAKERESTORENODE)); + if(!pNode) + return FALSE; + + pNode->hwnd = hwnd; + + pNode->place.length = sizeof(WINDOWPLACEMENT); + pNode->place.flags = WPF_ASYNCWINDOWPLACEMENT; + GetWindowPlacement(hwnd, &pNode->place); + tPlace = pNode->place; + tPlace.showCmd = SW_SHOWMINNOACTIVE; + SetWindowPlacement(hwnd, &tPlace); + + pNode->next = sizemove->shake.restoreList; + sizemove->shake.restoreList = pNode; + } + return TRUE; +} + +BOOL ForEachShakeNodeRestore(PSHAKERESTORENODE node, LPARAM ignore) +{ + if(node) + { + SetWindowPlacement(node->hwnd, &node->place); + LocalFree(node); + return TRUE; + } + return FALSE; +} + +void ShakeCheck(const MSG *lpmsg, SIZEMOVEDATA *sizemove) +{ + static DWORD dwShakeDisabled = 2; + WORD dir = 0; + int dx, dy; + if(lpmsg->message != WM_MOUSEMOVE && lpmsg->message != WM_NCMOUSEMOVE) + return; + if(dwShakeDisabled == 2) + { + HKEY hKey; + DWORD dwSizeDW = 4; + DWORD dwType = REG_DWORD; + if(/* major version number */LOBYTE(LOWORD(GetVersion())) < 6) + { + dwShakeDisabled = 1; + } + else + { + dwShakeDisabled = 0; + if(RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Policies\\Microsoft\\Windows\\Explorer", 0, KEY_READ, &hKey) + == ERROR_SUCCESS) + { + RegQueryValueExA(hKey, "NoWindowMinimizingShortcuts", 0, &dwType, (LPBYTE)&dwShakeDisabled, &dwSizeDW); + RegCloseKey(hKey); + } + } + } + if(dwShakeDisabled) + return; + if(lpmsg->time - sizemove->shake.dwPrevTime < LSM_SHAKE_MINTIME) + return; + + dx = lpmsg->pt.x - sizemove->ptCapture.x; + dy = lpmsg->pt.y - sizemove->ptCapture.y; + if(dx > 8) + dir |= LSM_LEFT; + else if(dx < -8) + dir |= LSM_RIGHT; + if(dy > 8) + dir |= LSM_TOP; + else if(dy < -8) + dir |= LSM_BOTTOM; + + if(dir && ((lpmsg->time - sizemove->shake.dwPrevTime) < LSM_SHAKE_MAXTIME) && (dir != sizemove->shake.wDir)) + { + /* Do Shake */ + sizemove->shake.wCount++; + if(sizemove->shake.wCount >= 3) + { + if(sizemove->shake.restoreList) + { + ForEachShakeNode(&sizemove->shake, ForEachShakeNodeRestore, (LPARAM)sizemove); + sizemove->shake.restoreList = 0; + } + else + { + EnumWindows(EnumWindowsShakeMinimize, (LPARAM)(sizemove)); + } + RedrawWindow(GetDesktopWindow(), NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INTERNALPAINT | RDW_INVALIDATE + | RDW_ALLCHILDREN | RDW_UPDATENOW); + sizemove->shake.wCount = 0; + } + } + else + { + sizemove->shake.wCount = 0; + } + sizemove->shake.dwPrevTime = lpmsg->time; + sizemove->shake.wDir = dir; +} + +void SnapCheck(POINT pt, SIZEMOVEDATA *sizemove) +{ + static DWORD dwSnapActive = 2; + HMONITOR monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo; + + if(dwSnapActive == 2) + { + HKEY hKey; + DWORD dwSizeDW = 4; + DWORD dwType = REG_DWORD; + dwSnapActive = 0; + if(/* major version number */LOBYTE(LOWORD(GetVersion())) >= 6) + { + if(RegOpenKeyExA(HKEY_CURRENT_USER, "Control Panel\\Desktop", 0, KEY_READ, &hKey) + == ERROR_SUCCESS) + { + RegQueryValueExA(hKey, "WindowArrangementActive", 0, &dwType, (LPBYTE)&dwSnapActive, &dwSizeDW); + RegCloseKey(hKey); + } + } + } + if(!dwSnapActive) + return; + + minfo.cbSize = sizeof(MONITORINFO); + if(monitor == NULL) + return; + if(!GetMonitorInfo(monitor, (LPMONITORINFO)&minfo)) + return; + /* unsnap maximized windows */ + if(IsZoomed(sizemove->hwnd)) + { + WINDOWPLACEMENT place; + int dx, w; + place.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(sizemove->hwnd, &place); + place.showCmd = SW_RESTORE; + OffsetRect(&place.rcNormalPosition, (sizemove->rcWin.left - place.rcNormalPosition.left), + (sizemove->rcWin.top - place.rcNormalPosition.top)); + w = RECTWIDTH(place.rcNormalPosition); + if((dx = (place.rcNormalPosition.left - sizemove->ptCapture.x)) > 0) + { + place.rcNormalPosition.left += dx << 1; + place.rcNormalPosition.right = place.rcNormalPosition.left + w; + } + if((dx = (sizemove->ptCapture.x - place.rcNormalPosition.right)) > 0) + { + place.rcNormalPosition.left += dx << 1; + place.rcNormalPosition.right = place.rcNormalPosition.left + w; + } + sizemove->rcWin = place.rcNormalPosition; + SetWindowPlacement(sizemove->hwnd, &place); + /* don't return here, it's possible to unsnap without leaving the top of the screen */ + } + else if(sizemove->snap.isSnapped) + { + /* TODO: Aero snap lets you drag half-screen snapped windows across the top of the screen */ + + sizemove->rcWin.right = sizemove->snap.rcRestore.right; + sizemove->rcWin.bottom = sizemove->snap.rcRestore.bottom; + sizemove->snap.isSnapped = 0; + return; + } + sizemove->snap.rcWork = minfo.rcWork; + if(pt.x <= minfo.rcWork.left) + { + /* left side stretch */ + sizemove->snap.rcWork.right = (RECTWIDTH(minfo.rcWork) - GetSystemMetrics(SM_CXFRAME)) >> 1; + sizemove->snap.snapType = LSM_LEFT; + } + else if(pt.x+1 >= minfo.rcWork.right) + { + /* right side stretch */ + sizemove->snap.rcWork.left = (minfo.rcWork.left + RECTWIDTH(minfo.rcWork) + GetSystemMetrics(SM_CXFRAME)) >> 1; + sizemove->snap.snapType = LSM_RIGHT; + } + else if(pt.y <= minfo.rcWork.top) + { + /* full stretch */ + sizemove->snap.snapType = LSM_TOP; + } + else + { + /* not snapping, clean up snap state */ + SnapCleanup(sizemove); + return; + } + SetWindowPos(sizemove->snap.helperWindow, HWND_TOPMOST, + sizemove->snap.rcWork.left, sizemove->snap.rcWork.top, + RECTWIDTH(sizemove->snap.rcWork), RECTHEIGHT(sizemove->snap.rcWork), + SWP_NOACTIVATE); + ShowWindow(sizemove->snap.helperWindow, SW_SHOWNA); +} + +void SnapFinalize(SIZEMOVEDATA *sizemove) +{ + if(sizemove->snap.rcWork.left != sizemove->snap.rcWork.right + && sizemove->snap.rcWork.top != sizemove->snap.rcWork.bottom) + { + if(sizemove->snap.snapType == LSM_TOP) + { + /* Aero appears to animate this... so we will too */ + ShowWindow(sizemove->hwnd, SW_MAXIMIZE); + } + else + { + SetWindowPos(sizemove->hwnd, 0, sizemove->snap.rcWork.left, sizemove->snap.rcWork.top, + RECTWIDTH(sizemove->snap.rcWork), RECTHEIGHT(sizemove->snap.rcWork), 0); + ZeroMemory(&sizemove->snap.rcRestore, sizeof(RECT)); + sizemove->snap.rcRestore.right = RECTWIDTH(sizemove->rcWin); + sizemove->snap.rcRestore.bottom = RECTWIDTH(sizemove->rcWin); + sizemove->snap.isSnapped = 1; + } + } + SnapCleanup(sizemove); +} + +BOOLEAN SizingCheck(const MSG *lpmsg) +{ + SIZEMOVEDATA *sizemove = LSMGet(); + POINT pt = lpmsg->pt; + int dx = 0, dy = 0; + /* + Discussion of rev3 changes. + There was a bug in previous revisions that would cause + the resize state to continue even if the Window lost + mouse capture. Windows provides notification of losing + mouse capture, but it crashes the program to take capture + back while processing that message. + So, we choose to yield to this other program and stop resizing. + This is probably user32 behavior anyway. + Windows also sends this notification in response to calling ReleaseCapture, + so all clean-up code has been moved to the handler. + This also allowed us to eliminate the function StopSizing. + */ + if(!sizemove) /* not sizing */ + return 0; + if(sizemove->grab == LSM_NOGRAB) /* not sizing */ + return 0; + if(lpmsg->hwnd != sizemove->hwnd) /* wrong window */ + return 0; + if(lpmsg->message == WM_NCLBUTTONUP || lpmsg->message == WM_LBUTTONUP) + { + SnapFinalize(sizemove); + ReleaseCapture(); + return 1; + } + if(lpmsg->message == WM_KEYDOWN) + { + switch(lpmsg->wParam) + { + case VK_RETURN: + ReleaseCapture(); + return 1; + case VK_ESCAPE: + { + SetWindowPos(sizemove->hwnd, 0, sizemove->rcOrig.left, sizemove->rcOrig.top, + RECTWIDTH(sizemove->rcOrig), RECTHEIGHT(sizemove->rcOrig), 0); + ReleaseCapture(); + return 1; + } + case VK_UP: + pt.y-=8; + break; + case VK_DOWN: + pt.y+=8; + break; + case VK_LEFT: + pt.x-=8; + break; + case VK_RIGHT: + pt.x+=8; + break; + default: + break; + } + } + + /* used to handle WM_MOUSEMOVE. This was unnecessary code */ + + dx = pt.x - sizemove->ptCapture.x; + dy = pt.y - sizemove->ptCapture.y; + if(dx || dy) + { + BOOL changeCursor = (lpmsg->message == WM_KEYDOWN); + WPARAM wpHit = 0; +#ifdef TIME_LOOP + LARGE_INTEGER pfBegin; + LARGE_INTEGER pfDraw; + LARGE_INTEGER pfFinal; + LARGE_INTEGER pfFreq; + QueryPerformanceFrequency(&pfFreq); + QueryPerformanceCounter(&pfBegin); +#endif + + if(sizemove->grab == LSM_CAPTION) + { + ShakeCheck(lpmsg, sizemove); + SnapCheck(pt, sizemove); + OffsetRect(&sizemove->rcWin, dx, dy); + } + else + { + /* note on minmax correction + if you do not correct the capture pos (set later from `pt`), + window will expand massively if user pulls back mouse + after failing to shrink when resizing from the + bottom or the right borders. + */ + /* when resizing using keys, Windows also moves the cursor */ + if(sizemove->grab & LSM_LEFT) + { + int lmax = sizemove->rcWin.right - sizemove->minmax.ptMaxTrackSize.x; + int lmin = sizemove->rcWin.right - sizemove->minmax.ptMinTrackSize.x; + if(sizemove->rcWin.left + dx < lmax) + { + sizemove->rcWin.left = lmax; + } + else if(sizemove->rcWin.left + dx > lmin) + { + sizemove->rcWin.left = lmin; + } + else + { + sizemove->rcWin.left += dx; + } + pt.x = sizemove->rcWin.left; + wpHit = WMSZ_LEFT; + } + else if(sizemove->grab & LSM_RIGHT) + { + int rmax = sizemove->rcWin.left + sizemove->minmax.ptMaxTrackSize.x; + int rmin = sizemove->rcWin.left + sizemove->minmax.ptMinTrackSize.x; + if(sizemove->rcWin.right + dx > rmax) + { + sizemove->rcWin.right = rmax; + } + else if(sizemove->rcWin.right + dx < rmin) + { + sizemove->rcWin.right = rmin; + } + else + { + sizemove->rcWin.right += dx; + } + pt.x = sizemove->rcWin.right; + wpHit = WMSZ_RIGHT; + } + if(sizemove->grab & LSM_TOP) + { + int tmax = sizemove->rcWin.bottom - sizemove->minmax.ptMaxTrackSize.y; + int tmin = sizemove->rcWin.bottom - sizemove->minmax.ptMinTrackSize.y; + if(sizemove->rcWin.top + dy < tmax) + { + sizemove->rcWin.top = tmax; + } + else if(sizemove->rcWin.top + dy > tmin) + { + sizemove->rcWin.top = tmin; + } + else + { + sizemove->rcWin.top += dy; + } + pt.y = sizemove->rcWin.top; + if(wpHit == WMSZ_LEFT) + { + wpHit = WMSZ_TOPLEFT; + } + else if(wpHit == WMSZ_RIGHT) + { + wpHit = WMSZ_TOPRIGHT; + } + else + { + wpHit = WMSZ_TOP; + } + } + else if(sizemove->grab & LSM_BOTTOM) + { + int bmax = sizemove->rcWin.top + sizemove->minmax.ptMaxTrackSize.y; + int bmin = sizemove->rcWin.top + sizemove->minmax.ptMinTrackSize.y; + if(sizemove->rcWin.bottom + dy > bmax) + { + sizemove->rcWin.bottom = bmax; + } + else if(sizemove->rcWin.bottom + dy < bmin) + { + sizemove->rcWin.bottom = bmin; + } + else + { + sizemove->rcWin.bottom += dy; + } + pt.y = sizemove->rcWin.bottom; + if(wpHit == WMSZ_LEFT) + { + wpHit = WMSZ_BOTTOMLEFT; + } + else if(wpHit == WMSZ_RIGHT) + { + wpHit = WMSZ_BOTTOMRIGHT; + } + else + { + wpHit = WMSZ_BOTTOM; + } + } + } +#ifdef TIME_LOOP + QueryPerformanceCounter(&pfDraw); +#endif + SendMessageAW(sizemove->hwnd, WM_SIZING, wpHit, (LPARAM)&sizemove->rcWin); + SetWindowPos(sizemove->hwnd, 0, sizemove->rcWin.left, sizemove->rcWin.top, + RECTWIDTH(sizemove->rcWin), RECTHEIGHT(sizemove->rcWin), 0); + + sizemove->ptCapture = pt; + /* when resizing using keys, Windows also moves the cursor */ + if(changeCursor) + SetCursorPos(pt.x, pt.y); +#ifdef TIME_LOOP + QueryPerformanceCounter(&pfFinal); + if(1) + { + double msTotal, msDraw; + msTotal = (double)(*(long long*)(&pfFinal) - *(long long *)(&pfBegin)) / (*(long long *)(&pfFreq) / 1000); + msDraw = (double)(*(long long*)(&pfFinal) - *(long long *)(&pfDraw)) / (*(long long *)(&pfFreq) / 1000); + printf("draw time: %.03fms\ntotal: %.03fms\n", msDraw, msTotal); + } +#endif + } + return 1; +} + +void LSMCleanup() +{ + SIZEMOVEDATA *sm = LSMGet(); + if(sm) + { + if(sm->shake.restoreList) + { + ForEachShakeNode(&sm->shake, ForEachShakeNodeFree, 0); + } + if(sm->snap.helperWindow) + { + DestroyWindow(sm->snap.helperWindow); + } + LocalFree(sm); + LSMSet(NULL); + } +} diff --git a/src/external/cJSON.c b/src/external/cJSON.c new file mode 100644 index 0000000..ded7006 --- /dev/null +++ b/src/external/cJSON.c @@ -0,0 +1,2980 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <limits.h> +#include <ctype.h> + +#ifdef ENABLE_LOCALES +#include <locale.h> +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { + if (!cJSON_IsString(item)) { + return NULL; + } + + return item->valuestring; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 12) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + return (fabs(a - b) <= CJSON_DOUBLE_PRECISION); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (!compare_double(d * 0, 0)) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = strlen((const char*)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL)) + { + return false; + } + + child = array->child; + + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + } + else + { + /* append to the end */ + while (child->next) + { + child = child->next; + } + suffix_object(child, item); + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return; + } + + add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return; + } + + add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item->prev != NULL) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + add_item_to_array(array, newitem); + return; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (parent->child == item) + { + parent->child = replacement; + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return; + } + + cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} + diff --git a/src/external/cJSON.h b/src/external/cJSON.h new file mode 100644 index 0000000..23d7851 --- /dev/null +++ b/src/external/cJSON.h @@ -0,0 +1,294 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 12 + +#include <stddef.h> + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Precision of double variables comparison */ +#ifndef CJSON_DOUBLE_PRECISION +#define CJSON_DOUBLE_PRECISION .0000000000000001 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check if the item is a string and return its valuestring */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable adress area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/external/stb_image.h b/src/external/stb_image.h new file mode 100644 index 0000000..a6202a3 --- /dev/null +++ b/src/external/stb_image.h @@ -0,0 +1,7547 @@ +/* stb_image - v2.22 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan + Dave Moore Roy Eltham Hayaki Saito Nathan Reed + Won Chun Luke Graham Johan Duparc Nick Verigakis + the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar + Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex + Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 + Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus + Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo + Christian Floisand Kevin Schmidt JR Smith github:darealshinji + Blazej Dariusz Roszkowski github:Michaelangel007 +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// + + +#ifndef STBI_NO_STDIO +#include <stdio.h> +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include <stdlib.h> +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF 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); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF 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); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include <stdarg.h> +#include <stddef.h> // ptrdiff_t on osx +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include <math.h> // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include <stdio.h> +#endif + +#ifndef STBI_ASSERT +#include <assert.h> +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include <stdint.h> +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include <emmintrin.h> + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include <intrin.h> // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include <arm_neon.h> +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF 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) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF 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) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<<n) + 1 +static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767}; + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) +{ + unsigned int k; + int sgn; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static 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) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + 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 + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#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]); } +#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)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#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]); } +#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]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + 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; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + 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; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + 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; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + 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"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + 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 + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + 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"); + 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"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static 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) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static 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) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 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 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 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}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + 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 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 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 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static 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) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static 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) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + 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"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + 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 + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1; z >>= 1; } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v >= 0 && v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; y<height; ++y) { + int packet_idx; + + for(packet_idx=0; packet_idx < num_packets; ++packet_idx) { + stbi__pic_packet *packet = &packets[packet_idx]; + stbi_uc *dest = result+y*width*4; + + switch (packet->type) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;x<width;++x, dest+=4) + if (!stbi__readval(s,packet->channel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; i<count; ++i,dest+=4) + stbi__copyval(packet->channel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;i<count;++i, dest += 4) + stbi__copyval(packet->channel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;i<count;++i, dest+=4) + if (!stbi__readval(s,packet->channel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/external/stb_truetype.h b/src/external/stb_truetype.h new file mode 100644 index 0000000..767f005 --- /dev/null +++ b/src/external/stb_truetype.h @@ -0,0 +1,4882 @@ +// stb_truetype.h - v1.21 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1). +// +// Advancing for the next character: +// Call GlyphHMetrics, and compute 'current_point += SF * advance'. +// +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include <math.h> + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include <math.h> + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <string.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i<lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); + stbtt_uint8 *lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); + stbtt_uint8 *subTableOffsets = lookupTable + 6; + switch(lookupType) { + case 2: { // Pair Adjustment Positioning Subtable + stbtt_int32 sti; + for (sti=0; sti<subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); + stbtt_uint8 *table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(table); + stbtt_uint16 coverageOffset = ttUSHORT(table + 2); + stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); + if (coverageIndex == -1) continue; + + switch (posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(table + 8); + stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); + stbtt_uint8 *pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); + stbtt_uint8 *pairValueArray = pairValueTable + 2; + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + STBTT_assert(coverageIndex < pairSetCount); + STBTT__NOTUSED(pairSetCount); + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && spc->skip_missing) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/external/utf8.h b/src/external/utf8.h new file mode 100644 index 0000000..1554884 --- /dev/null +++ b/src/external/utf8.h @@ -0,0 +1,1258 @@ +// The latest version of this library is available on GitHub; +// https://github.com/sheredom/utf8.h + +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to <http://unlicense.org/> + +#ifndef SHEREDOM_UTF8_H_INCLUDED +#define SHEREDOM_UTF8_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +// disable 'bytes padding added after construct' warning +#pragma warning(disable : 4820) +#endif + +#include <stddef.h> +#include <stdlib.h> + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) +typedef __int32 utf8_int32_t; +#else +#include <stdint.h> +typedef int32_t utf8_int32_t; +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define utf8_nonnull __attribute__((nonnull)) +#define utf8_pure __attribute__((pure)) +#define utf8_restrict __restrict__ +#define utf8_weak __attribute__((weak)) +#elif defined(_MSC_VER) +#define utf8_nonnull +#define utf8_pure +#define utf8_restrict __restrict +#define utf8_weak __inline +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __cplusplus +#define utf8_null NULL +#else +#define utf8_null 0 +#endif + + // Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + // src2 respectively, case insensitive. + utf8_nonnull utf8_pure utf8_weak int utf8casecmp(const void *src1, + const void *src2); + + // Append the utf8 string src onto the utf8 string dst. + utf8_nonnull utf8_weak void *utf8cat(void *utf8_restrict dst, + const void *utf8_restrict src); + + // Find the first match of the utf8 codepoint chr in the utf8 string src. + utf8_nonnull utf8_pure utf8_weak void *utf8chr(const void *src, + utf8_int32_t chr); + + // Return less than 0, 0, greater than 0 if src1 < src2, + // src1 == src2, src1 > src2 respectively. + utf8_nonnull utf8_pure utf8_weak int utf8cmp(const void *src1, + const void *src2); + + // Copy the utf8 string src onto the memory allocated in dst. + utf8_nonnull utf8_weak void *utf8cpy(void *utf8_restrict dst, + const void *utf8_restrict src); + + // Number of utf8 codepoints in the utf8 string src that consists entirely + // of utf8 codepoints not from the utf8 string reject. + utf8_nonnull utf8_pure utf8_weak size_t utf8cspn(const void *src, + const void *reject); + + // Duplicate the utf8 string src by getting its size, malloc'ing a new buffer + // copying over the data, and returning that. Or 0 if malloc failed. + utf8_nonnull utf8_weak void *utf8dup(const void *src); + + // Number of utf8 codepoints in the utf8 string str, + // excluding the null terminating byte. + utf8_nonnull utf8_pure utf8_weak size_t utf8len(const void *str); + + // Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + // src2 respectively, case insensitive. Checking at most n bytes of each utf8 + // string. + utf8_nonnull utf8_pure utf8_weak int utf8ncasecmp(const void *src1, + const void *src2, size_t n); + + // Append the utf8 string src onto the utf8 string dst, + // writing at most n+1 bytes. Can produce an invalid utf8 + // string if n falls partway through a utf8 codepoint. + utf8_nonnull utf8_weak void *utf8ncat(void *utf8_restrict dst, + const void *utf8_restrict src, size_t n); + + // Return less than 0, 0, greater than 0 if src1 < src2, + // src1 == src2, src1 > src2 respectively. Checking at most n + // bytes of each utf8 string. + utf8_nonnull utf8_pure utf8_weak int utf8ncmp(const void *src1, + const void *src2, size_t n); + + // Copy the utf8 string src onto the memory allocated in dst. + // Copies at most n bytes. If there is no terminating null byte in + // the first n bytes of src, the string placed into dst will not be + // null-terminated. If the size (in bytes) of src is less than n, + // extra null terminating bytes are appended to dst such that at + // total of n bytes are written. Can produce an invalid utf8 + // string if n falls partway through a utf8 codepoint. + utf8_nonnull utf8_weak void *utf8ncpy(void *utf8_restrict dst, + const void *utf8_restrict src, size_t n); + + // Similar to utf8dup, except that at most n bytes of src are copied. If src is + // longer than n, only n bytes are copied and a null byte is added. + // + // Returns a new string if successful, 0 otherwise + utf8_nonnull utf8_weak void *utf8ndup(const void *src, size_t n); + + // Locates the first occurence in the utf8 string str of any byte in the + // utf8 string accept, or 0 if no match was found. + utf8_nonnull utf8_pure utf8_weak void *utf8pbrk(const void *str, + const void *accept); + + // Find the last match of the utf8 codepoint chr in the utf8 string src. + utf8_nonnull utf8_pure utf8_weak void *utf8rchr(const void *src, int chr); + + // Number of bytes in the utf8 string str, + // including the null terminating byte. + utf8_nonnull utf8_pure utf8_weak size_t utf8size(const void *str); + + // Number of utf8 codepoints in the utf8 string src that consists entirely + // of utf8 codepoints from the utf8 string accept. + utf8_nonnull utf8_pure utf8_weak size_t utf8spn(const void *src, + const void *accept); + + // The position of the utf8 string needle in the utf8 string haystack. + utf8_nonnull utf8_pure utf8_weak void *utf8str(const void *haystack, + const void *needle); + + // The position of the utf8 string needle in the utf8 string haystack, case + // insensitive. + utf8_nonnull utf8_pure utf8_weak void *utf8casestr(const void *haystack, + const void *needle); + + // Return 0 on success, or the position of the invalid + // utf8 codepoint on failure. + utf8_nonnull utf8_pure utf8_weak void *utf8valid(const void *str); + + // Sets out_codepoint to the next utf8 codepoint in str, and returns the address + // of the utf8 codepoint after the current one in str. + utf8_nonnull utf8_weak void * + utf8codepoint(const void *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + + // Returns the size of the given codepoint in bytes. + utf8_weak size_t utf8codepointsize(utf8_int32_t chr); + + // Write a codepoint to the given string, and return the address to the next + // place after the written codepoint. Pass how many bytes left in the buffer to + // n. If there is not enough space for the codepoint, this function returns + // null. + utf8_nonnull utf8_weak void *utf8catcodepoint(void *utf8_restrict str, + utf8_int32_t chr, size_t n); + + // Returns 1 if the given character is lowercase, or 0 if it is not. + utf8_weak int utf8islower(utf8_int32_t chr); + + // Returns 1 if the given character is uppercase, or 0 if it is not. + utf8_weak int utf8isupper(utf8_int32_t chr); + + // Transform the given string into all lowercase codepoints. + utf8_nonnull utf8_weak void utf8lwr(void *utf8_restrict str); + + // Transform the given string into all uppercase codepoints. + utf8_nonnull utf8_weak void utf8upr(void *utf8_restrict str); + + // Make a codepoint lower case if possible. + utf8_weak utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); + + // Make a codepoint upper case if possible. + utf8_weak utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); + +#undef utf8_weak +#undef utf8_pure +#undef utf8_nonnull + + int utf8casecmp(const void *src1, const void *src2) { + utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp; + + for (;;) { + src1 = utf8codepoint(src1, &src1_cp); + src2 = utf8codepoint(src2, &src2_cp); + + // take a copy of src1 & src2 + src1_orig_cp = src1_cp; + src2_orig_cp = src2_cp; + + // lower the srcs if required + src1_cp = utf8lwrcodepoint(src1_cp); + src2_cp = utf8lwrcodepoint(src2_cp); + + // check if the lowered codepoints match + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if (src1_cp == src2_cp) { + continue; + } + + // if they don't match, then we return the difference between the characters + return src1_cp - src2_cp; + } + } + + void *utf8cat(void *utf8_restrict dst, const void *utf8_restrict src) { + char *d = (char *)dst; + const char *s = (const char *)src; + + // find the null terminating byte in dst + while ('\0' != *d) { + d++; + } + + // overwriting the null terminating byte in dst, append src byte-by-byte + while ('\0' != *s) { + *d++ = *s++; + } + + // write out a new null terminating byte into dst + *d = '\0'; + + return dst; + } + + void *utf8chr(const void *src, utf8_int32_t chr) { + char c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + // being asked to return position of null terminating byte, so + // just run s to the end, and return! + const char *s = (const char *)src; + while ('\0' != *s) { + s++; + } + return (void *)s; + } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + // 1-byte/7-bit ascii + // (0b0xxxxxxx) + c[0] = (char)chr; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + // 2-byte/11-bit utf8 code point + // (0b110xxxxx 0b10xxxxxx) + c[0] = 0xc0 | (char)(chr >> 6); + c[1] = 0x80 | (char)(chr & 0x3f); + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + // 3-byte/16-bit utf8 code point + // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) + c[0] = 0xe0 | (char)(chr >> 12); + c[1] = 0x80 | (char)((chr >> 6) & 0x3f); + c[2] = 0x80 | (char)(chr & 0x3f); + } else { // if (0 == ((int)0xffe00000 & chr)) { + // 4-byte/21-bit utf8 code point + // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) + c[0] = 0xf0 | (char)(chr >> 18); + c[1] = 0x80 | (char)((chr >> 12) & 0x3f); + c[2] = 0x80 | (char)((chr >> 6) & 0x3f); + c[3] = 0x80 | (char)(chr & 0x3f); + } + + // we've made c into a 2 utf8 codepoint string, one for the chr we are + // seeking, another for the null terminating byte. Now use utf8str to + // search + return utf8str(src, c); + } + + int utf8cmp(const void *src1, const void *src2) { + const unsigned char *s1 = (const unsigned char *)src1; + const unsigned char *s2 = (const unsigned char *)src2; + + while (('\0' != *s1) || ('\0' != *s2)) { + if (*s1 < *s2) { + return -1; + } else if (*s1 > *s2) { + return 1; + } + + s1++; + s2++; + } + + // both utf8 strings matched + return 0; + } + + int utf8coll(const void *src1, const void *src2); + + void *utf8cpy(void *utf8_restrict dst, const void *utf8_restrict src) { + char *d = (char *)dst; + const char *s = (const char *)src; + + // overwriting anything previously in dst, write byte-by-byte + // from src + while ('\0' != *s) { + *d++ = *s++; + } + + // append null terminating byte + *d = '\0'; + + return dst; + } + + size_t utf8cspn(const void *src, const void *reject) { + const char *s = (const char *)src; + size_t chars = 0; + + while ('\0' != *s) { + const char *r = (const char *)reject; + size_t offset = 0; + + while ('\0' != *r) { + // checking that if *r is the start of a utf8 codepoint + // (it is not 0b10xxxxxx) and we have successfully matched + // a previous character (0 < offset) - we found a match + if ((0x80 != (0xc0 & *r)) && (0 < offset)) { + return chars; + } else { + if (*r == s[offset]) { + // part of a utf8 codepoint matched, so move our checking + // onwards to the next byte + offset++; + r++; + } else { + // r could be in the middle of an unmatching utf8 code point, + // so we need to march it on to the next character beginning, + + do { + r++; + } while (0x80 == (0xc0 & *r)); + + // reset offset too as we found a mismatch + offset = 0; + } + } + } + + // the current utf8 codepoint in src did not match reject, but src + // could have been partway through a utf8 codepoint, so we need to + // march it onto the next utf8 codepoint starting byte + do { + s++; + } while ((0x80 == (0xc0 & *s))); + chars++; + } + + return chars; + } + + size_t utf8size(const void *str); + + void *utf8dup(const void *src) { + const char *s = (const char *)src; + char *n = utf8_null; + + // figure out how many bytes (including the terminator) we need to copy first + size_t bytes = utf8size(src); + + n = (char *)malloc(bytes); + + if (utf8_null == n) { + // out of memory so we bail + return utf8_null; + } else { + bytes = 0; + + // copy src byte-by-byte into our new utf8 string + while ('\0' != s[bytes]) { + n[bytes] = s[bytes]; + bytes++; + } + + // append null terminating byte + n[bytes] = '\0'; + return n; + } + } + + void *utf8fry(const void *str); + + size_t utf8len(const void *str) { + const unsigned char *s = (const unsigned char *)str; + size_t length = 0; + + while ('\0' != *s) { + if (0xf0 == (0xf8 & *s)) { + // 4-byte utf8 code point (began with 0b11110xxx) + s += 4; + } else if (0xe0 == (0xf0 & *s)) { + // 3-byte utf8 code point (began with 0b1110xxxx) + s += 3; + } else if (0xc0 == (0xe0 & *s)) { + // 2-byte utf8 code point (began with 0b110xxxxx) + s += 2; + } else { // if (0x00 == (0x80 & *s)) { + // 1-byte ascii (began with 0b0xxxxxxx) + s += 1; + } + + // no matter the bytes we marched s forward by, it was + // only 1 utf8 codepoint + length++; + } + + return length; + } + + int utf8ncasecmp(const void *src1, const void *src2, size_t n) { + utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp; + + do { + const unsigned char *const s1 = (const unsigned char *)src1; + const unsigned char *const s2 = (const unsigned char *)src2; + + // first check that we have enough bytes left in n to contain an entire + // codepoint + if (0 == n) { + return 0; + } + + if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) { + const utf8_int32_t c1 = (0xe0 & *s1); + const utf8_int32_t c2 = (0xe0 & *s2); + + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } else { + return 0; + } + } + + if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) { + const utf8_int32_t c1 = (0xf0 & *s1); + const utf8_int32_t c2 = (0xf0 & *s2); + + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } else { + return 0; + } + } + + if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) { + const utf8_int32_t c1 = (0xf8 & *s1); + const utf8_int32_t c2 = (0xf8 & *s2); + + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } else { + return 0; + } + } + + src1 = utf8codepoint(src1, &src1_cp); + src2 = utf8codepoint(src2, &src2_cp); + n -= utf8codepointsize(src1_cp); + + // Take a copy of src1 & src2 + src1_orig_cp = src1_cp; + src2_orig_cp = src2_cp; + + // Lower srcs if required + src1_cp = utf8lwrcodepoint(src1_cp); + src2_cp = utf8lwrcodepoint(src2_cp); + + // Check if the lowered codepoints match + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if (src1_cp == src2_cp) { + continue; + } + + // If they don't match, then we return which of the original's are less + if (src1_orig_cp < src2_orig_cp) { + return -1; + } else if (src1_orig_cp > src2_orig_cp) { + return 1; + } + } while (0 < n); + + // both utf8 strings matched + return 0; + } + + void *utf8ncat(void *utf8_restrict dst, const void *utf8_restrict src, + size_t n) { + char *d = (char *)dst; + const char *s = (const char *)src; + + // find the null terminating byte in dst + while ('\0' != *d) { + d++; + } + + // overwriting the null terminating byte in dst, append src byte-by-byte + // stopping if we run out of space + do { + *d++ = *s++; + } while (('\0' != *s) && (0 != --n)); + + // write out a new null terminating byte into dst + *d = '\0'; + + return dst; + } + + int utf8ncmp(const void *src1, const void *src2, size_t n) { + const unsigned char *s1 = (const unsigned char *)src1; + const unsigned char *s2 = (const unsigned char *)src2; + + while ((0 != n--) && (('\0' != *s1) || ('\0' != *s2))) { + if (*s1 < *s2) { + return -1; + } else if (*s1 > *s2) { + return 1; + } + + s1++; + s2++; + } + + // both utf8 strings matched + return 0; + } + + void *utf8ncpy(void *utf8_restrict dst, const void *utf8_restrict src, + size_t n) { + char *d = (char *)dst; + const char *s = (const char *)src; + size_t index; + + // overwriting anything previously in dst, write byte-by-byte + // from src + for (index = 0; index < n; index++) { + d[index] = s[index]; + if ('\0' == s[index]) { + break; + } + } + + // append null terminating byte + for (; index < n; index++) { + d[index] = 0; + } + + return dst; + } + + void *utf8ndup(const void *src, size_t n) { + const char *s = (const char *)src; + char *c = utf8_null; + size_t bytes = 0; + + // Find the end of the string or stop when n is reached + while ('\0' != s[bytes] && bytes < n) { + bytes++; + } + + // In case bytes is actually less than n, we need to set it + // to be used later in the copy byte by byte. + n = bytes; + + c = (char *)malloc(bytes + 1); + if (utf8_null == c) { + // out of memory so we bail + return utf8_null; + } + + bytes = 0; + + // copy src byte-by-byte into our new utf8 string + while ('\0' != s[bytes] && bytes < n) { + c[bytes] = s[bytes]; + bytes++; + } + + // append null terminating byte + c[bytes] = '\0'; + return c; + } + + void *utf8rchr(const void *src, int chr) { + const char *s = (const char *)src; + const char *match = utf8_null; + char c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + // being asked to return position of null terminating byte, so + // just run s to the end, and return! + while ('\0' != *s) { + s++; + } + return (void *)s; + } else if (0 == ((int)0xffffff80 & chr)) { + // 1-byte/7-bit ascii + // (0b0xxxxxxx) + c[0] = (char)chr; + } else if (0 == ((int)0xfffff800 & chr)) { + // 2-byte/11-bit utf8 code point + // (0b110xxxxx 0b10xxxxxx) + c[0] = 0xc0 | (char)(chr >> 6); + c[1] = 0x80 | (char)(chr & 0x3f); + } else if (0 == ((int)0xffff0000 & chr)) { + // 3-byte/16-bit utf8 code point + // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) + c[0] = 0xe0 | (char)(chr >> 12); + c[1] = 0x80 | (char)((chr >> 6) & 0x3f); + c[2] = 0x80 | (char)(chr & 0x3f); + } else { // if (0 == ((int)0xffe00000 & chr)) { + // 4-byte/21-bit utf8 code point + // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) + c[0] = 0xf0 | (char)(chr >> 18); + c[1] = 0x80 | (char)((chr >> 12) & 0x3f); + c[2] = 0x80 | (char)((chr >> 6) & 0x3f); + c[3] = 0x80 | (char)(chr & 0x3f); + } + + // we've created a 2 utf8 codepoint string in c that is + // the utf8 character asked for by chr, and a null + // terminating byte + + while ('\0' != *s) { + size_t offset = 0; + + while (s[offset] == c[offset]) { + offset++; + } + + if ('\0' == c[offset]) { + // we found a matching utf8 code point + match = s; + s += offset; + } else { + s += offset; + + // need to march s along to next utf8 codepoint start + // (the next byte that doesn't match 0b10xxxxxx) + if ('\0' != *s) { + do { + s++; + } while (0x80 == (0xc0 & *s)); + } + } + } + + // return the last match we found (or 0 if no match was found) + return (void *)match; + } + + void *utf8pbrk(const void *str, const void *accept) { + const char *s = (const char *)str; + + while ('\0' != *s) { + const char *a = (const char *)accept; + size_t offset = 0; + + while ('\0' != *a) { + // checking that if *a is the start of a utf8 codepoint + // (it is not 0b10xxxxxx) and we have successfully matched + // a previous character (0 < offset) - we found a match + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + return (void *)s; + } else { + if (*a == s[offset]) { + // part of a utf8 codepoint matched, so move our checking + // onwards to the next byte + offset++; + a++; + } else { + // r could be in the middle of an unmatching utf8 code point, + // so we need to march it on to the next character beginning, + + do { + a++; + } while (0x80 == (0xc0 & *a)); + + // reset offset too as we found a mismatch + offset = 0; + } + } + } + + // we found a match on the last utf8 codepoint + if (0 < offset) { + return (void *)s; + } + + // the current utf8 codepoint in src did not match accept, but src + // could have been partway through a utf8 codepoint, so we need to + // march it onto the next utf8 codepoint starting byte + do { + s++; + } while ((0x80 == (0xc0 & *s))); + } + + return utf8_null; + } + + size_t utf8size(const void *str) { + const char *s = (const char *)str; + size_t size = 0; + while ('\0' != s[size]) { + size++; + } + + // we are including the null terminating byte in the size calculation + size++; + return size; + } + + size_t utf8spn(const void *src, const void *accept) { + const char *s = (const char *)src; + size_t chars = 0; + + while ('\0' != *s) { + const char *a = (const char *)accept; + size_t offset = 0; + + while ('\0' != *a) { + // checking that if *r is the start of a utf8 codepoint + // (it is not 0b10xxxxxx) and we have successfully matched + // a previous character (0 < offset) - we found a match + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + // found a match, so increment the number of utf8 codepoints + // that have matched and stop checking whether any other utf8 + // codepoints in a match + chars++; + s += offset; + break; + } else { + if (*a == s[offset]) { + offset++; + a++; + } else { + // a could be in the middle of an unmatching utf8 codepoint, + // so we need to march it on to the next character beginning, + do { + a++; + } while (0x80 == (0xc0 & *a)); + + // reset offset too as we found a mismatch + offset = 0; + } + } + } + + // if a got to its terminating null byte, then we didn't find a match. + // Return the current number of matched utf8 codepoints + if ('\0' == *a) { + return chars; + } + } + + return chars; + } + + void *utf8str(const void *haystack, const void *needle) { + const char *h = (const char *)haystack; + utf8_int32_t throwaway_codepoint; + + // if needle has no utf8 codepoints before the null terminating + // byte then return haystack + if ('\0' == *((const char *)needle)) { + return (void *)haystack; + } + + while ('\0' != *h) { + const char *maybeMatch = h; + const char *n = (const char *)needle; + + while (*h == *n && (*h != '\0' && *n != '\0')) { + n++; + h++; + } + + if ('\0' == *n) { + // we found the whole utf8 string for needle in haystack at + // maybeMatch, so return it + return (void *)maybeMatch; + } else { + // h could be in the middle of an unmatching utf8 codepoint, + // so we need to march it on to the next character beginning + // starting from the current character + h = (const char*)utf8codepoint(maybeMatch, &throwaway_codepoint); + } + } + + // no match + return utf8_null; + } + + void *utf8casestr(const void *haystack, const void *needle) { + const void *h = haystack; + + // if needle has no utf8 codepoints before the null terminating + // byte then return haystack + if ('\0' == *((const char *)needle)) { + return (void *)haystack; + } + + for (;;) { + const void *maybeMatch = h; + const void *n = needle; + utf8_int32_t h_cp, n_cp; + + // Get the next code point and track it + const void *nextH = h = utf8codepoint(h, &h_cp); + n = utf8codepoint(n, &n_cp); + + while ((0 != h_cp) && (0 != n_cp)) { + h_cp = utf8lwrcodepoint(h_cp); + n_cp = utf8lwrcodepoint(n_cp); + + // if we find a mismatch, bail out! + if (h_cp != n_cp) { + break; + } + + h = utf8codepoint(h, &h_cp); + n = utf8codepoint(n, &n_cp); + } + + if (0 == n_cp) { + // we found the whole utf8 string for needle in haystack at + // maybeMatch, so return it + return (void *)maybeMatch; + } + + if (0 == h_cp) { + // no match + return utf8_null; + } + + // Roll back to the next code point in the haystack to test + h = nextH; + } + } + + void *utf8valid(const void *str) { + const char *s = (const char *)str; + + while ('\0' != *s) { + if (0xf0 == (0xf8 & *s)) { + // ensure each of the 3 following bytes in this 4-byte + // utf8 codepoint began with 0b10xxxxxx + if ((0x80 != (0xc0 & s[1])) || (0x80 != (0xc0 & s[2])) || + (0x80 != (0xc0 & s[3]))) { + return (void *)s; + } + + // ensure that our utf8 codepoint ended after 4 bytes + if (0x80 == (0xc0 & s[4])) { + return (void *)s; + } + + // ensure that the top 5 bits of this 4-byte utf8 + // codepoint were not 0, as then we could have used + // one of the smaller encodings + if ((0 == (0x07 & s[0])) && (0 == (0x30 & s[1]))) { + return (void *)s; + } + + // 4-byte utf8 code point (began with 0b11110xxx) + s += 4; + } else if (0xe0 == (0xf0 & *s)) { + // ensure each of the 2 following bytes in this 3-byte + // utf8 codepoint began with 0b10xxxxxx + if ((0x80 != (0xc0 & s[1])) || (0x80 != (0xc0 & s[2]))) { + return (void *)s; + } + + // ensure that our utf8 codepoint ended after 3 bytes + if (0x80 == (0xc0 & s[3])) { + return (void *)s; + } + + // ensure that the top 5 bits of this 3-byte utf8 + // codepoint were not 0, as then we could have used + // one of the smaller encodings + if ((0 == (0x0f & s[0])) && (0 == (0x20 & s[1]))) { + return (void *)s; + } + + // 3-byte utf8 code point (began with 0b1110xxxx) + s += 3; + } else if (0xc0 == (0xe0 & *s)) { + // ensure the 1 following byte in this 2-byte + // utf8 codepoint began with 0b10xxxxxx + if (0x80 != (0xc0 & s[1])) { + return (void *)s; + } + + // ensure that our utf8 codepoint ended after 2 bytes + if (0x80 == (0xc0 & s[2])) { + return (void *)s; + } + + // ensure that the top 4 bits of this 2-byte utf8 + // codepoint were not 0, as then we could have used + // one of the smaller encodings + if (0 == (0x1e & s[0])) { + return (void *)s; + } + + // 2-byte utf8 code point (began with 0b110xxxxx) + s += 2; + } else if (0x00 == (0x80 & *s)) { + // 1-byte ascii (began with 0b0xxxxxxx) + s += 1; + } else { + // we have an invalid 0b1xxxxxxx utf8 code point entry + return (void *)s; + } + } + + return utf8_null; + } + + void *utf8codepoint(const void *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + const char *s = (const char *)str; + + if (0xf0 == (0xf8 & s[0])) { + // 4 byte utf8 codepoint + *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | + ((0x3f & s[2]) << 6) | (0x3f & s[3]); + s += 4; + } else if (0xe0 == (0xf0 & s[0])) { + // 3 byte utf8 codepoint + *out_codepoint = + ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); + s += 3; + } else if (0xc0 == (0xe0 & s[0])) { + // 2 byte utf8 codepoint + *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); + s += 2; + } else { + // 1 byte utf8 codepoint otherwise + *out_codepoint = s[0]; + s += 1; + } + + return (void *)s; + } + + size_t utf8codepointsize(utf8_int32_t chr) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + return 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + return 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + return 3; + } else { // if (0 == ((int)0xffe00000 & chr)) { + return 4; + } + } + + void *utf8catcodepoint(void *utf8_restrict str, utf8_int32_t chr, size_t n) { + char *s = (char *)str; + + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + // 1-byte/7-bit ascii + // (0b0xxxxxxx) + if (n < 1) { + return utf8_null; + } + s[0] = (char)chr; + s += 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + // 2-byte/11-bit utf8 code point + // (0b110xxxxx 0b10xxxxxx) + if (n < 2) { + return utf8_null; + } + s[0] = 0xc0 | (char)(chr >> 6); + s[1] = 0x80 | (char)(chr & 0x3f); + s += 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + // 3-byte/16-bit utf8 code point + // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) + if (n < 3) { + return utf8_null; + } + s[0] = 0xe0 | (char)(chr >> 12); + s[1] = 0x80 | (char)((chr >> 6) & 0x3f); + s[2] = 0x80 | (char)(chr & 0x3f); + s += 3; + } else { // if (0 == ((int)0xffe00000 & chr)) { + // 4-byte/21-bit utf8 code point + // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) + if (n < 4) { + return utf8_null; + } + s[0] = 0xf0 | (char)(chr >> 18); + s[1] = 0x80 | (char)((chr >> 12) & 0x3f); + s[2] = 0x80 | (char)((chr >> 6) & 0x3f); + s[3] = 0x80 | (char)(chr & 0x3f); + s += 4; + } + + return s; + } + + int utf8islower(utf8_int32_t chr) { return chr != utf8uprcodepoint(chr); } + + int utf8isupper(utf8_int32_t chr) { return chr != utf8lwrcodepoint(chr); } + + void utf8lwr(void *utf8_restrict str) { + void *p, *pn; + utf8_int32_t cp; + + p = (char *)str; + pn = utf8codepoint(p, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(p, lwr_cp, size); + } + + p = pn; + pn = utf8codepoint(p, &cp); + } + } + + void utf8upr(void *utf8_restrict str) { + void *p, *pn; + utf8_int32_t cp; + + p = (char *)str; + pn = utf8codepoint(p, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8uprcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(p, lwr_cp, size); + } + + p = pn; + pn = utf8codepoint(p, &cp); + } + } + + utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) { + if (((0x0041 <= cp) && (0x005a >= cp)) || + ((0x00c0 <= cp) && (0x00d6 >= cp)) || + ((0x00d8 <= cp) && (0x00de >= cp)) || + ((0x0391 <= cp) && (0x03a1 >= cp)) || + ((0x03a3 <= cp) && (0x03ab >= cp))) { + cp += 32; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp))) { + cp |= 0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp += 1; + cp &= ~0x1; + } else { + switch (cp) { + default: break; + case 0x0178: cp = 0x00ff; break; + case 0x0243: cp = 0x0180; break; + case 0x018e: cp = 0x01dd; break; + case 0x023d: cp = 0x019a; break; + case 0x0220: cp = 0x019e; break; + case 0x01b7: cp = 0x0292; break; + case 0x01c4: cp = 0x01c6; break; + case 0x01c7: cp = 0x01c9; break; + case 0x01ca: cp = 0x01cc; break; + case 0x01f1: cp = 0x01f3; break; + case 0x01f7: cp = 0x01bf; break; + case 0x0187: cp = 0x0188; break; + case 0x018b: cp = 0x018c; break; + case 0x0191: cp = 0x0192; break; + case 0x0198: cp = 0x0199; break; + case 0x01a7: cp = 0x01a8; break; + case 0x01ac: cp = 0x01ad; break; + case 0x01af: cp = 0x01b0; break; + case 0x01b8: cp = 0x01b9; break; + case 0x01bc: cp = 0x01bd; break; + case 0x01f4: cp = 0x01f5; break; + case 0x023b: cp = 0x023c; break; + case 0x0241: cp = 0x0242; break; + case 0x03fd: cp = 0x037b; break; + case 0x03fe: cp = 0x037c; break; + case 0x03ff: cp = 0x037d; break; + case 0x037f: cp = 0x03f3; break; + case 0x0386: cp = 0x03ac; break; + case 0x0388: cp = 0x03ad; break; + case 0x0389: cp = 0x03ae; break; + case 0x038a: cp = 0x03af; break; + case 0x038c: cp = 0x03cc; break; + case 0x038e: cp = 0x03cd; break; + case 0x038f: cp = 0x03ce; break; + case 0x0370: cp = 0x0371; break; + case 0x0372: cp = 0x0373; break; + case 0x0376: cp = 0x0377; break; + case 0x03f4: cp = 0x03d1; break; + case 0x03cf: cp = 0x03d7; break; + case 0x03f9: cp = 0x03f2; break; + case 0x03f7: cp = 0x03f8; break; + case 0x03fa: cp = 0x03fb; break; + }; + } + + return cp; + } + + utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { + if (((0x0061 <= cp) && (0x007a >= cp)) || + ((0x00e0 <= cp) && (0x00f6 >= cp)) || + ((0x00f8 <= cp) && (0x00fe >= cp)) || + ((0x03b1 <= cp) && (0x03c1 >= cp)) || + ((0x03c3 <= cp) && (0x03cb >= cp))) { + cp -= 32; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp))) { + cp &= ~0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp -= 1; + cp |= 0x1; + } else { + switch (cp) { + default: break; + case 0x00ff: cp = 0x0178; break; + case 0x0180: cp = 0x0243; break; + case 0x01dd: cp = 0x018e; break; + case 0x019a: cp = 0x023d; break; + case 0x019e: cp = 0x0220; break; + case 0x0292: cp = 0x01b7; break; + case 0x01c6: cp = 0x01c4; break; + case 0x01c9: cp = 0x01c7; break; + case 0x01cc: cp = 0x01ca; break; + case 0x01f3: cp = 0x01f1; break; + case 0x01bf: cp = 0x01f7; break; + case 0x0188: cp = 0x0187; break; + case 0x018c: cp = 0x018b; break; + case 0x0192: cp = 0x0191; break; + case 0x0199: cp = 0x0198; break; + case 0x01a8: cp = 0x01a7; break; + case 0x01ad: cp = 0x01ac; break; + case 0x01b0: cp = 0x01af; break; + case 0x01b9: cp = 0x01b8; break; + case 0x01bd: cp = 0x01bc; break; + case 0x01f5: cp = 0x01f4; break; + case 0x023c: cp = 0x023b; break; + case 0x0242: cp = 0x0241; break; + case 0x037b: cp = 0x03fd; break; + case 0x037c: cp = 0x03fe; break; + case 0x037d: cp = 0x03ff; break; + case 0x03f3: cp = 0x037f; break; + case 0x03ac: cp = 0x0386; break; + case 0x03ad: cp = 0x0388; break; + case 0x03ae: cp = 0x0389; break; + case 0x03af: cp = 0x038a; break; + case 0x03cc: cp = 0x038c; break; + case 0x03cd: cp = 0x038e; break; + case 0x03ce: cp = 0x038f; break; + case 0x0371: cp = 0x0370; break; + case 0x0373: cp = 0x0372; break; + case 0x0377: cp = 0x0376; break; + case 0x03d1: cp = 0x03f4; break; + case 0x03d7: cp = 0x03cf; break; + case 0x03f2: cp = 0x03f9; break; + case 0x03f8: cp = 0x03f7; break; + case 0x03fb: cp = 0x03fa; break; + }; + } + + return cp; + } + +#undef utf8_restrict +#undef utf8_null + +#ifdef __cplusplus +} // extern "C" +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // SHEREDOM_UTF8_H_INCLUDED diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..40f7b74 --- /dev/null +++ b/src/input.c @@ -0,0 +1,237 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +inline bool is_left_down(mouse_input *input) +{ + return input->left_state & MOUSE_DOWN; +} + +inline bool is_left_released(mouse_input *input) +{ + return input->left_state & MOUSE_RELEASE; +} + +inline bool is_left_clicked(mouse_input *input) +{ + return input->left_state & MOUSE_CLICK; +} + +inline bool is_left_double_clicked(mouse_input *input) +{ + return input->left_state & MOUSE_DOUBLE_CLICK; +} + +inline bool is_right_down(mouse_input *input) +{ + return input->right_state & MOUSE_DOWN; +} + +inline bool is_right_released(mouse_input *input) +{ + return input->right_state & MOUSE_RELEASE; +} + +inline bool is_right_clicked(mouse_input *input) +{ + return input->right_state & MOUSE_CLICK; +} + +inline bool keyboard_is_key_down(keyboard_input *keyboard, s16 key) +{ + return keyboard->keys[key]; +} + +inline bool keyboard_is_key_pressed(keyboard_input *keyboard, s16 key) +{ + return keyboard->input_keys[key]; +} + +inline void keyboard_set_input_text(keyboard_input *keyboard, char *text) +{ + string_copyn(keyboard->input_text, text, MAX_INPUT_LENGTH); + u32 len = utf8len(keyboard->input_text); + keyboard->cursor = len; + keyboard->input_text_len = len; +} + +mouse_input mouse_input_create() +{ + mouse_input mouse; + mouse.x = -1; + mouse.y = -1; + mouse.move_x = 0; + mouse.move_y = 0; + mouse.left_state = 0; + mouse.right_state = 0; + + return mouse; +} + +keyboard_input keyboard_input_create() +{ + keyboard_input keyboard; + keyboard.modifier_state = 0; + keyboard.input_text = mem_alloc(MAX_INPUT_LENGTH); + keyboard.input_text[0] = '\0'; + keyboard.take_input = false; + keyboard.cursor = 0; + keyboard.input_text_len = 0; + keyboard.input_mode = INPUT_FULL; + keyboard.has_selection = false; + keyboard.selection_begin_offset = 0; + keyboard.selection_length = 0; + memset(keyboard.keys, 0, MAX_KEYCODE); + + return keyboard; +} + +void keyboard_set_input_mode(keyboard_input *keyboard, keyboard_input_mode mode) +{ + keyboard->input_mode = mode; +} + +inline void keyboard_input_destroy(keyboard_input *keyboard) +{ + mem_free(keyboard->input_text); +} + +static void keyboard_handle_input_copy_and_paste(platform_window *window, keyboard_input *keyboard) +{ + bool is_lctrl_down = keyboard->keys[KEY_LEFT_CONTROL]; + + if (is_lctrl_down && keyboard_is_key_pressed(keyboard, KEY_V)) + { + char buf[MAX_INPUT_LENGTH]; + bool result = platform_get_clipboard(window, buf); + + if (keyboard->input_mode == INPUT_NUMERIC && !is_string_numeric(buf)) + { + return; + } + + if (keyboard->has_selection) + { + keyboard->cursor = keyboard->selection_begin_offset; + utf8_str_remove_range(keyboard->input_text, keyboard->selection_begin_offset, + keyboard->selection_begin_offset + keyboard->selection_length); + keyboard->has_selection = false; + keyboard->text_changed = true; + } + + if (result) + { + s32 len = utf8len(buf); + utf8_str_insert_utf8str(keyboard->input_text, keyboard->cursor, buf); + + keyboard->cursor += len; + keyboard->input_text_len += len; + keyboard->text_changed = true; + } + } + else if (is_lctrl_down && keyboard_is_key_pressed(keyboard, KEY_C)) + { + char buffer[MAX_INPUT_LENGTH]; + utf8_str_copy_range(keyboard->input_text, + keyboard->selection_begin_offset, + keyboard->selection_begin_offset+keyboard->selection_length, + buffer); + + if (!string_equals(buffer, "")) + platform_set_clipboard(window, buffer); + } +} + +void keyboard_handle_input_string(platform_window *window, keyboard_input *keyboard, char *ch) +{ + keyboard_handle_input_copy_and_paste(window, keyboard); + + bool is_lctrl_down = keyboard->keys[KEY_LEFT_CONTROL]; + + if (ch) + { + if (keyboard->input_text_len < MAX_INPUT_LENGTH && !is_lctrl_down) + { + if (keyboard->has_selection) + { + keyboard->cursor = keyboard->selection_begin_offset; + utf8_str_remove_range(keyboard->input_text, keyboard->selection_begin_offset, + keyboard->selection_begin_offset + keyboard->selection_length); + keyboard->has_selection = false; + keyboard->text_changed = true; + } + + if (keyboard->input_text_len) + { + utf8_str_insert_at(keyboard->input_text, keyboard->cursor, *ch); + keyboard->text_changed = true; + } + else + { + string_appendn(keyboard->input_text, ch, MAX_INPUT_LENGTH); + keyboard->text_changed = true; + } + + keyboard->cursor++; + keyboard->input_text_len = utf8len(keyboard->input_text); + } + } + else + { + bool is_lctrl_down = keyboard->keys[KEY_LEFT_CONTROL]; + + // cursor movement + if (keyboard_is_key_down(keyboard, KEY_LEFT) && keyboard->cursor > 0) + { + if (is_lctrl_down) + keyboard->cursor = 0; + else + keyboard->cursor--; + } + if (keyboard_is_key_down(keyboard, KEY_RIGHT) && keyboard->cursor < keyboard->input_text_len) + { + if (is_lctrl_down) + keyboard->cursor = utf8len(keyboard->input_text); + else + keyboard->cursor++; + } + } + + if (keyboard_is_key_down(keyboard, KEY_BACKSPACE)) + { + bool is_lctrl_down = keyboard->keys[KEY_LEFT_CONTROL]; + + if (keyboard->has_selection) + { + utf8_str_remove_range(keyboard->input_text, keyboard->selection_begin_offset, + keyboard->selection_begin_offset + keyboard->selection_length); + keyboard->has_selection = false; + keyboard->text_changed = true; + + if (keyboard->selection_begin_offset < keyboard->cursor) + { + keyboard->cursor -= keyboard->selection_length-1; + } + } + else if (is_lctrl_down) + { + for (s32 i = 0; i < keyboard->cursor; i++) + { + utf8_str_remove_at(keyboard->input_text, 0); + } + keyboard->cursor = 0; + keyboard->text_changed = true; + } + else if (keyboard->cursor > 0) + { + utf8_str_remove_at(keyboard->input_text, keyboard->cursor-1); + + keyboard->cursor--; + keyboard->text_changed = true; + } + + keyboard->input_text_len = utf8len(keyboard->input_text); + } +}
\ No newline at end of file diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..21f4df9 --- /dev/null +++ b/src/input.h @@ -0,0 +1,224 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_INPUT +#define INCLUDE_INPUT + +/* The unknown key */ +#define KEY_UNKNOWN -1 + +#define MOUSE_OFFSCREEN 32767 + +/* Printable keys */ +#define KEY_SPACE 32 +#define KEY_APOSTROPHE 39 /* ' */ +#define KEY_COMMA 44 /* , */ +#define KEY_MINUS 45 /* - */ +#define KEY_PERIOD 46 /* . */ +#define KEY_SLASH 47 /* / */ +#define KEY_0 48 +#define KEY_1 49 +#define KEY_2 50 +#define KEY_3 51 +#define KEY_4 52 +#define KEY_5 53 +#define KEY_6 54 +#define KEY_7 55 +#define KEY_8 56 +#define KEY_9 57 +#define KEY_SEMICOLON 59 /* ; */ +#define KEY_EQUAL 61 /* = */ +#define KEY_A 65 +#define KEY_B 66 +#define KEY_C 67 +#define KEY_D 68 +#define KEY_E 69 +#define KEY_F 70 +#define KEY_G 71 +#define KEY_H 72 +#define KEY_I 73 +#define KEY_J 74 +#define KEY_K 75 +#define KEY_L 76 +#define KEY_M 77 +#define KEY_N 78 +#define KEY_O 79 +#define KEY_P 80 +#define KEY_Q 81 +#define KEY_R 82 +#define KEY_S 83 +#define KEY_T 84 +#define KEY_U 85 +#define KEY_V 86 +#define KEY_W 87 +#define KEY_X 88 +#define KEY_Y 89 +#define KEY_Z 90 +#define KEY_LEFT_BRACKET 91 /* [ */ +#define KEY_BACKSLASH 92 /* \ */ +#define KEY_RIGHT_BRACKET 93 /* ] */ +#define KEY_GRAVE_ACCENT 96 /* ` */ +#define KEY_WORLD_1 161 /* non-US #1 */ +#define KEY_WORLD_2 162 /* non-US #2 */ + +/* Function keys */ +#define KEY_ESCAPE 256 +#define KEY_ENTER 257 +#define KEY_TAB 258 +#define KEY_BACKSPACE 259 +#define KEY_INSERT 260 +#define KEY_DELETE 261 +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_PAGE_UP 266 +#define KEY_PAGE_DOWN 267 +#define KEY_HOME 268 +#define KEY_END 269 +#define KEY_CAPS_LOCK 280 +#define KEY_SCROLL_LOCK 281 +#define KEY_NUM_LOCK 282 +#define KEY_PRINT_SCREEN 283 +#define KEY_PAUSE 284 +#define KEY_F1 290 +#define KEY_F2 291 +#define KEY_F3 292 +#define KEY_F4 293 +#define KEY_F5 294 +#define KEY_F6 295 +#define KEY_F7 296 +#define KEY_F8 297 +#define KEY_F9 298 +#define KEY_F10 299 +#define KEY_F11 300 +#define KEY_F12 301 +#define KEY_F13 302 +#define KEY_F14 303 +#define KEY_F15 304 +#define KEY_F16 305 +#define KEY_F17 306 +#define KEY_F18 307 +#define KEY_F19 308 +#define KEY_F20 309 +#define KEY_F21 310 +#define KEY_F22 311 +#define KEY_F23 312 +#define KEY_F24 313 +#define KEY_F25 314 +#define KEY_KP_0 320 +#define KEY_KP_1 321 +#define KEY_KP_2 322 +#define KEY_KP_3 323 +#define KEY_KP_4 324 +#define KEY_KP_5 325 +#define KEY_KP_6 326 +#define KEY_KP_7 327 +#define KEY_KP_8 328 +#define KEY_KP_9 329 +#define KEY_KP_DECIMAL 330 +#define KEY_KP_DIVIDE 331 +#define KEY_KP_MULTIPLY 332 +#define KEY_KP_SUBTRACT 333 +#define KEY_KP_ADD 334 +#define KEY_KP_ENTER 335 +#define KEY_KP_EQUAL 336 +#define KEY_LEFT_SHIFT 340 +#define KEY_LEFT_CONTROL 341 +#define KEY_LEFT_ALT 342 +#define KEY_LEFT_SUPER 343 +#define KEY_RIGHT_SHIFT 344 +#define KEY_RIGHT_CONTROL 345 +#define KEY_RIGHT_ALT 346 +#define KEY_RIGHT_SUPER 347 +#define KEY_MENU 348 + +#define KEY_LAST KEY_MENU + +#define MAX_KEYCODE 512 + +#define MOUSE_DOWN (1 << 1) +#define MOUSE_RELEASE (1 << 2) +#define MOUSE_DOUBLE_CLICK (1 << 3) +#define MOUSE_CLICK (1 << 4) + +#define SCROLL_UP 1 +#define SCROLL_DOWN -1 + + +// should be max path length +#ifdef OS_LINUX +#define MAX_INPUT_LENGTH 4096+1 +#define MAX_PATH_LENGTH 255+1 +#endif + +#ifdef OS_WIN +#define MAX_INPUT_LENGTH 4096+1 +#define MAX_PATH_LENGTH 259+1 +#endif + +typedef struct t_mouse_input +{ + s16 x; + s16 y; + s16 move_x; + s16 move_y; + s16 total_move_x; + s16 total_move_y; + s8 left_state; + s8 right_state; + s8 scroll_state; +} mouse_input; + +typedef enum t_keyboard_input_mode +{ + INPUT_NUMERIC, + INPUT_FULL, +} keyboard_input_mode; + +typedef struct t_keyboard_input +{ + keyboard_input_mode input_mode; + int modifier_state; + bool take_input; + u32 cursor; + + // input + bool text_changed; // is set when text is pasted in, incase the new text is the same length as the old text so we know to store a new undo entry + bool has_selection; + s32 selection_begin_offset; + s32 selection_length; + char *input_text; + // input + + s32 input_text_len; + bool keys[MAX_KEYCODE]; + bool input_keys[MAX_KEYCODE]; +} keyboard_input; + +int keycode_map[MAX_KEYCODE]; + +bool is_left_down(mouse_input *input); +bool is_left_released(mouse_input *input); +bool is_left_clicked(mouse_input *input); +bool is_left_double_clicked(mouse_input *input); +bool is_right_down(mouse_input *input); +bool is_right_released(mouse_input *input); +bool is_right_clicked(mouse_input *input); + +bool keyboard_is_key_down(keyboard_input *keyboard, s16 key); +bool keyboard_is_key_pressed(keyboard_input *keyboard, s16 key); +void keyboard_set_input_text(keyboard_input *keyboard, char *text); +void keyboard_set_input_mode(keyboard_input *keyboard, keyboard_input_mode mode); + +typedef struct t_platform_window platform_window; +void keyboard_handle_input_string(platform_window *window, keyboard_input *keyboard, char *text); + +mouse_input mouse_input_create(); +keyboard_input keyboard_input_create(); +void keyboard_input_destroy(keyboard_input *keyboard); + +#endif
\ No newline at end of file diff --git a/src/languages.h b/src/languages.h new file mode 100644 index 0000000..a079141 --- /dev/null +++ b/src/languages.h @@ -0,0 +1,22 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#define LANGUAGE_CODE_SIZE 3 + +typedef struct t_language +{ + char code[3]; + char fullname[40]; +} language; + + +language global_langues[] = { + {"AD","Andorra"}, + {"AE","United Arab Emirates"}, + {"AF","Afghanistan"}, +}; + +#define COUNTRY_CODE_COUNT (sizeof(global_langues)/sizeof(language))
\ No newline at end of file diff --git a/src/linux/platform.c b/src/linux/platform.c new file mode 100644 index 0000000..e650097 --- /dev/null +++ b/src/linux/platform.c @@ -0,0 +1,1592 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xrandr.h> +#include <X11/Xatom.h> +#include <time.h> +#include <X11/XKBlib.h> +#include <unistd.h> +#include <limits.h> +#include <dirent.h> +#include <errno.h> +#include <dlfcn.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <X11/cursorfont.h> + +#define GET_ATOM(X) window.X = XInternAtom(window.display, #X, False) + +struct t_platform_window +{ + Display *display; + Window parent; + XVisualInfo *visual_info; + Colormap cmap; + Window window; + GLXContext gl_context; + XWindowAttributes window_attributes; + XEvent event; + char *clipboard_str; + s32 clipboard_strlen; + + Atom xdnd_req; + Atom xdnd_source; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndTypeList; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XdndLeave; + Atom quit; + Atom PRIMARY; + Atom CLIPBOARD; + Atom UTF8_STRING; + Atom COMPOUND_STRING; + Atom TARGETS; + Atom MULTIPLE; + Atom _NET_WM_STATE; + + // shared window properties + s32 width; + s32 height; + bool is_open; + bool has_focus; + cursor_type curr_cursor_type; + cursor_type next_cursor_type; +}; + +bool platform_get_clipboard(platform_window *window, char *buffer) +{ + char *result; + unsigned long ressize, restail; + int resbits; + Atom bufid = XInternAtom(window->display, "CLIPBOARD", False), + fmtid = XInternAtom(window->display, "UTF8_STRING", False), + propid = XInternAtom(window->display, "XSEL_DATA", False), + incrid = XInternAtom(window->display, "INCR", False); + XEvent event; + + if(window->CLIPBOARD != None) { + + if (settings_window && XGetSelectionOwner(window->display, window->CLIPBOARD) == settings_window->window) + { + snprintf(buffer, MAX_INPUT_LENGTH, "%s", settings_window->clipboard_str); + return true; + } + else if (XGetSelectionOwner(window->display, window->CLIPBOARD) == + main_window->window) + { + snprintf(buffer, MAX_INPUT_LENGTH, "%s", main_window->clipboard_str); + return true; + } + } + + XConvertSelection(window->display, bufid, fmtid, propid, window->window, CurrentTime); + do { + XNextEvent(window->display, &event); + } while (event.type != SelectionNotify || event.xselection.selection != bufid); + + if (event.xselection.property) + { + XGetWindowProperty(window->display, window->window, propid, 0, LONG_MAX/4, False, AnyPropertyType, + &fmtid, &resbits, &ressize, &restail, (unsigned char**)&result); + + if (fmtid == incrid) + printf("Buffer is too large and INCR reading is not implemented yet.\n"); + else + snprintf(buffer, MAX_INPUT_LENGTH, "%s", result); + + XFree(result); + return True; + } + else // request failed, e.g. owner can't convert to the target format + return False; +} + +bool platform_set_clipboard(platform_window *window, char *buffer) +{ + if (buffer) + { + if(window->CLIPBOARD != None && XGetSelectionOwner(window->display, window->CLIPBOARD) != window->window) { + XSetSelectionOwner(window->display, window->CLIPBOARD, window->window, CurrentTime); + } + + window->clipboard_strlen = strlen(buffer); + if(!window->clipboard_str) { + window->clipboard_str = mem_alloc(window->clipboard_strlen+1); + } else { + window->clipboard_str = mem_realloc(window->clipboard_str, window->clipboard_strlen+1); + } + string_copyn(window->clipboard_str, buffer, window->clipboard_strlen+1); + + return true; + } + + return false; +} + +void platform_create_config_directory() +{ + char *env = getenv("HOME"); + char tmp[PATH_MAX]; + snprintf(tmp, PATH_MAX, "%s%s", env, "/.config/text-search"); + + if (!platform_directory_exists(tmp)) + { + mkdir(tmp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + } +} + +char* get_config_save_location(char *buffer) +{ + char *env = getenv("HOME"); + snprintf(buffer, PATH_MAX, "%s%s", env, "/.config/text-search/config.txt"); + return buffer; +} + +inline void platform_set_cursor(platform_window *window, cursor_type type) +{ + if (window->next_cursor_type != type) + { + window->next_cursor_type = type; + } +} + +bool is_platform_in_darkmode() +{ + return false; +} + +bool get_active_directory(char *buffer) +{ + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + string_copyn(buffer, cwd, PATH_MAX); + } else { + return false; + } + return true; +} + +bool set_active_directory(char *path) +{ + return !chdir(path); +} + +bool platform_write_file_content(char *path, const char *mode, char *buffer, s32 len) +{ + bool result = false; + + FILE *file = fopen(path, mode); + + if (!file) + { + goto done_failure; + } + else + { + fprintf(file, "%s", buffer); + } + + //done: + fclose(file); + done_failure: + return result; +} + +void platform_window_set_title(platform_window *window, char *name) +{ + Atom WM_NAME = XInternAtom(window->display, "WM_NAME", False); + Atom _NET_WM_NAME = XInternAtom(window->display, "_NET_WM_NAME", False); + Atom _NET_WM_ICON_NAME = XInternAtom(window->display, "_NET_WM_ICON_NAME", False); + + char *list[1] = { (char *) name }; + XTextProperty property; + + XStoreName(window->display, window->window, name); + + Xutf8TextListToTextProperty(window->display, list, 1, XUTF8StringStyle, + &property); + XSetTextProperty(window->display, window->window, &property, WM_NAME); + XSetTextProperty(window->display, window->window, &property, _NET_WM_NAME); + XSetTextProperty(window->display, window->window, &property, XA_WM_NAME); + XSetTextProperty(window->display, window->window, &property, _NET_WM_ICON_NAME); + XFree(property.value); +} + +bool platform_file_exists(char *path) +{ + if(access(path, F_OK) != -1) { + return 1; + } + + return 0; +} + +bool platform_directory_exists(char *path) +{ + DIR* dir = opendir(path); + if (dir) { + /* Directory exists. */ + closedir(dir); + return 1; + } else if (ENOENT == errno) { + return 0; // does not exist + } else { + return 0; // error opening dir + } +} + +file_content platform_read_file_content(char *path, const char *mode) +{ + file_content result; + result.content = 0; + result.content_length = 0; + result.file_error = 0; + + FILE *file = fopen(path, mode); + + if (!file) + { + if (errno == EMFILE) + result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS; + else if (errno == ENFILE) + result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM; + else if (errno == EACCES) + result.file_error = FILE_ERROR_NO_ACCESS; + else if (errno == EPERM) + result.file_error = FILE_ERROR_NO_ACCESS; + else if (errno == ENOENT) + result.file_error = FILE_ERROR_NOT_FOUND; + else if (errno == ECONNABORTED) + result.file_error = FILE_ERROR_CONNECTION_ABORTED; + else if (errno == ECONNREFUSED) + result.file_error = FILE_ERROR_CONNECTION_REFUSED; + else if (errno == ENETDOWN) + result.file_error = FILE_ERROR_NETWORK_DOWN; + else if (errno == EREMOTEIO) + result.file_error = FILE_ERROR_REMOTE_IO_ERROR; + else if (errno == ESTALE) + result.file_error = FILE_ERROR_STALE; + else + { + result.file_error = FILE_ERROR_GENERIC; + printf("ERROR: %d\n", errno); + } + + goto done_failure; + } + + fseek(file, 0 , SEEK_END); + int length = ftell(file); + fseek(file, 0, SEEK_SET); + + s32 length_to_alloc = length+1; + + result.content = mem_alloc(length_to_alloc); + if (!result.content) goto done; + + memset(result.content, 0, length); + s32 read_result = fread(result.content, 1, length, file); + if (read_result == 0 && length != 0) + { + mem_free(result.content); + result.content = 0; + return result; + } + + result.content_length = read_result; + + ((char*)result.content)[length] = 0; + + done: + fclose(file); + done_failure: + return result; +} + +inline void platform_destroy_file_content(file_content *content) +{ + assert(content); + mem_free(content->content); +} + +// Translate an X11 key code to a GLFW key code. +// +static s32 translate_keycode(platform_window *window, s32 scancode) +{ + s32 keySym; + + // Valid key code range is [8,255], according to the Xlib manual + + if (1) + { + // Try secondary keysym, for numeric keypad keys + // Note: This way we always force "NumLock = ON", which is intentional + // since the returned key code should correspond to a physical + // location. + keySym = XkbKeycodeToKeysym(window->display, scancode, 0, 1); + switch (keySym) + { + case XK_KP_0: return KEY_KP_0; + case XK_KP_1: return KEY_KP_1; + case XK_KP_2: return KEY_KP_2; + case XK_KP_3: return KEY_KP_3; + case XK_KP_4: return KEY_KP_4; + case XK_KP_5: return KEY_KP_5; + case XK_KP_6: return KEY_KP_6; + case XK_KP_7: return KEY_KP_7; + case XK_KP_8: return KEY_KP_8; + case XK_KP_9: return KEY_KP_9; + case XK_KP_Separator: + case XK_KP_Decimal: return KEY_KP_DECIMAL; + case XK_KP_Equal: return KEY_KP_EQUAL; + case XK_KP_Enter: return KEY_KP_ENTER; + default: break; + } + + // Now try primary keysym for function keys (non-printable keys) + // These should not depend on the current keyboard layout + keySym = XkbKeycodeToKeysym(window->display, scancode, 0, 0); + } + + switch (keySym) + { + case XK_Escape: return KEY_ESCAPE; + case XK_Tab: return KEY_TAB; + case XK_Shift_L: return KEY_LEFT_SHIFT; + case XK_Shift_R: return KEY_RIGHT_SHIFT; + case XK_Control_L: return KEY_LEFT_CONTROL; + case XK_Control_R: return KEY_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return KEY_LEFT_ALT; + case XK_Mode_switch: // Mapped to Alt_R on many keyboards + case XK_ISO_Level3_Shift: // AltGr on at least some machines + case XK_Meta_R: + case XK_Alt_R: return KEY_RIGHT_ALT; + case XK_Super_L: return KEY_LEFT_SUPER; + case XK_Super_R: return KEY_RIGHT_SUPER; + case XK_Menu: return KEY_MENU; + case XK_Num_Lock: return KEY_NUM_LOCK; + case XK_Caps_Lock: return KEY_CAPS_LOCK; + case XK_Print: return KEY_PRINT_SCREEN; + case XK_Scroll_Lock: return KEY_SCROLL_LOCK; + case XK_Pause: return KEY_PAUSE; + case XK_Delete: return KEY_DELETE; + case XK_BackSpace: return KEY_BACKSPACE; + case XK_Return: return KEY_ENTER; + case XK_Home: return KEY_HOME; + case XK_End: return KEY_END; + case XK_Page_Up: return KEY_PAGE_UP; + case XK_Page_Down: return KEY_PAGE_DOWN; + case XK_Insert: return KEY_INSERT; + case XK_Left: return KEY_LEFT; + case XK_Right: return KEY_RIGHT; + case XK_Down: return KEY_DOWN; + case XK_Up: return KEY_UP; + case XK_F1: return KEY_F1; + case XK_F2: return KEY_F2; + case XK_F3: return KEY_F3; + case XK_F4: return KEY_F4; + case XK_F5: return KEY_F5; + case XK_F6: return KEY_F6; + case XK_F7: return KEY_F7; + case XK_F8: return KEY_F8; + case XK_F9: return KEY_F9; + case XK_F10: return KEY_F10; + case XK_F11: return KEY_F11; + case XK_F12: return KEY_F12; + case XK_F13: return KEY_F13; + case XK_F14: return KEY_F14; + case XK_F15: return KEY_F15; + case XK_F16: return KEY_F16; + case XK_F17: return KEY_F17; + case XK_F18: return KEY_F18; + case XK_F19: return KEY_F19; + case XK_F20: return KEY_F20; + case XK_F21: return KEY_F21; + case XK_F22: return KEY_F22; + case XK_F23: return KEY_F23; + case XK_F24: return KEY_F24; + case XK_F25: return KEY_F25; + + // Numeric keypad + case XK_KP_Divide: return KEY_KP_DIVIDE; + case XK_KP_Multiply: return KEY_KP_MULTIPLY; + case XK_KP_Subtract: return KEY_KP_SUBTRACT; + case XK_KP_Add: return KEY_KP_ADD; + + // These should have been detected in secondary keysym test above! + case XK_KP_Insert: return KEY_KP_0; + case XK_KP_End: return KEY_KP_1; + case XK_KP_Down: return KEY_KP_2; + case XK_KP_Page_Down: return KEY_KP_3; + case XK_KP_Left: return KEY_KP_4; + case XK_KP_Right: return KEY_KP_6; + case XK_KP_Home: return KEY_KP_7; + case XK_KP_Up: return KEY_KP_8; + case XK_KP_Page_Up: return KEY_KP_9; + case XK_KP_Delete: return KEY_KP_DECIMAL; + case XK_KP_Equal: return KEY_KP_EQUAL; + case XK_KP_Enter: return KEY_KP_ENTER; + + // Last resort: Check for printable keys (should not happen if the XKB + // extension is available). This will give a layout dependent mapping + // (which is wrong, and we may miss some keys, especially on non-US + // keyboards), but it's better than nothing... + case XK_a: return KEY_A; + case XK_b: return KEY_B; + case XK_c: return KEY_C; + case XK_d: return KEY_D; + case XK_e: return KEY_E; + case XK_f: return KEY_F; + case XK_g: return KEY_G; + case XK_h: return KEY_H; + case XK_i: return KEY_I; + case XK_j: return KEY_J; + case XK_k: return KEY_K; + case XK_l: return KEY_L; + case XK_m: return KEY_M; + case XK_n: return KEY_N; + case XK_o: return KEY_O; + case XK_p: return KEY_P; + case XK_q: return KEY_Q; + case XK_r: return KEY_R; + case XK_s: return KEY_S; + case XK_t: return KEY_T; + case XK_u: return KEY_U; + case XK_v: return KEY_V; + case XK_w: return KEY_W; + case XK_x: return KEY_X; + case XK_y: return KEY_Y; + case XK_z: return KEY_Z; + case XK_1: return KEY_1; + case XK_2: return KEY_2; + case XK_3: return KEY_3; + case XK_4: return KEY_4; + case XK_5: return KEY_5; + case XK_6: return KEY_6; + case XK_7: return KEY_7; + case XK_8: return KEY_8; + case XK_9: return KEY_9; + case XK_0: return KEY_0; + case XK_space: return KEY_SPACE; + case XK_minus: return KEY_MINUS; + case XK_equal: return KEY_EQUAL; + case XK_bracketleft: return KEY_LEFT_BRACKET; + case XK_bracketright: return KEY_RIGHT_BRACKET; + case XK_backslash: return KEY_BACKSLASH; + case XK_semicolon: return KEY_SEMICOLON; + case XK_apostrophe: return KEY_APOSTROPHE; + case XK_grave: return KEY_GRAVE_ACCENT; + case XK_comma: return KEY_COMMA; + case XK_period: return KEY_PERIOD; + case XK_slash: return KEY_SLASH; + case XK_less: return KEY_WORLD_1; // At least in some layouts... + default: break; + } + + // No matching translation was found + return KEY_UNKNOWN; +} + +static void create_key_tables(platform_window window) +{ + s32 scancode, key; + char name[XkbKeyNameLength + 1]; + XkbDescPtr desc = XkbGetMap(window.display, 0, XkbUseCoreKbd); + XkbGetNames(window.display, XkbKeyNamesMask, desc); + + // uncomment for layout independant input for games. +#if 0 + for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++) + { + memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength); + name[XkbKeyNameLength] = '\0'; + + // Map the key name to a GLFW key code. Note: We only map printable + // keys here, and we use the US keyboard layout. The rest of the + // keys (function keys) are mapped using traditional KeySym + // translations. + if (strcmp(name, "TLDE") == 0) key = KEY_GRAVE_ACCENT; + else if (strcmp(name, "AE01") == 0) key = KEY_1; + else if (strcmp(name, "AE02") == 0) key = KEY_2; + else if (strcmp(name, "AE03") == 0) key = KEY_3; + else if (strcmp(name, "AE04") == 0) key = KEY_4; + else if (strcmp(name, "AE05") == 0) key = KEY_5; + else if (strcmp(name, "AE06") == 0) key = KEY_6; + else if (strcmp(name, "AE07") == 0) key = KEY_7; + else if (strcmp(name, "AE08") == 0) key = KEY_8; + else if (strcmp(name, "AE09") == 0) key = KEY_9; + else if (strcmp(name, "AE10") == 0) key = KEY_0; + else if (strcmp(name, "AE11") == 0) key = KEY_MINUS; + else if (strcmp(name, "AE12") == 0) key = KEY_EQUAL; + else if (strcmp(name, "AD01") == 0) key = KEY_Q; + else if (strcmp(name, "AD02") == 0) key = KEY_W; + else if (strcmp(name, "AD03") == 0) key = KEY_E; + else if (strcmp(name, "AD04") == 0) key = KEY_R; + else if (strcmp(name, "AD05") == 0) key = KEY_T; + else if (strcmp(name, "AD06") == 0) key = KEY_Y; + else if (strcmp(name, "AD07") == 0) key = KEY_U; + else if (strcmp(name, "AD08") == 0) key = KEY_I; + else if (strcmp(name, "AD09") == 0) key = KEY_O; + else if (strcmp(name, "AD10") == 0) key = KEY_P; + else if (strcmp(name, "AD11") == 0) key = KEY_LEFT_BRACKET; + else if (strcmp(name, "AD12") == 0) key = KEY_RIGHT_BRACKET; + else if (strcmp(name, "AC01") == 0) key = KEY_A; + else if (strcmp(name, "AC02") == 0) key = KEY_S; + else if (strcmp(name, "AC03") == 0) key = KEY_D; + else if (strcmp(name, "AC04") == 0) key = KEY_F; + else if (strcmp(name, "AC05") == 0) key = KEY_G; + else if (strcmp(name, "AC06") == 0) key = KEY_H; + else if (strcmp(name, "AC07") == 0) key = KEY_J; + else if (strcmp(name, "AC08") == 0) key = KEY_K; + else if (strcmp(name, "AC09") == 0) key = KEY_L; + else if (strcmp(name, "AC10") == 0) key = KEY_SEMICOLON; + else if (strcmp(name, "AC11") == 0) key = KEY_APOSTROPHE; + else if (strcmp(name, "AB01") == 0) key = KEY_Z; + else if (strcmp(name, "AB02") == 0) key = KEY_X; + else if (strcmp(name, "AB03") == 0) key = KEY_C; + else if (strcmp(name, "AB04") == 0) key = KEY_V; + else if (strcmp(name, "AB05") == 0) key = KEY_B; + else if (strcmp(name, "AB06") == 0) key = KEY_N; + else if (strcmp(name, "AB07") == 0) key = KEY_M; + else if (strcmp(name, "AB08") == 0) key = KEY_COMMA; + else if (strcmp(name, "AB09") == 0) key = KEY_PERIOD; + else if (strcmp(name, "AB10") == 0) key = KEY_SLASH; + else if (strcmp(name, "BKSL") == 0) key = KEY_BACKSLASH; + else if (strcmp(name, "LSGT") == 0) key = KEY_WORLD_1; + else key = KEY_UNKNOWN; + + if ((scancode >= 0) && (scancode < 256)) + keycode_map[scancode] = key; + } +#endif + + for (scancode = 0; scancode < MAX_KEYCODE; scancode++) + { + // Translate the un-translated key codes using traditional X11 KeySym + // lookups + + keycode_map[scancode] = translate_keycode(&window, scancode); + } + + XkbFreeNames(desc, XkbKeyNamesMask, True); + XkbFreeKeyboard(desc, 0, True); +} + +inline void platform_init(int argc, char **argv) +{ +#if 0 + dlerror(); // clear error + void *x11 = dlopen("libX11.so.6", RTLD_NOW | RTLD_GLOBAL); + void *randr = dlopen("libXrandr.so", RTLD_NOW | RTLD_GLOBAL); +#endif + + setlocale(LC_ALL, "en_US.UTF-8"); + + XInitThreads(); + + // get fullpath of the directory the binary is residing in + binary_path = platform_get_full_path(argv[0]); + + platform_create_config_directory(); + + char buf[MAX_INPUT_LENGTH]; + get_directory_from_path(buf, binary_path); + string_copyn(binary_path, buf, MAX_INPUT_LENGTH); + + assets_create(); +} + +inline void platform_destroy() +{ + assets_destroy(); + +#if defined(MODE_DEVELOPER) + memory_print_leaks(); +#endif +} + +inline void platform_window_make_current(platform_window *window) +{ + glXMakeCurrent(window->display, window->window, window->gl_context); +} + +void platform_window_set_size(platform_window *window, u16 width, u16 height) +{ + XResizeWindow(window->display, window->window, width, height); +} + +void platform_window_set_position(platform_window *window, u16 x, u16 y) +{ + XMoveWindow(window->display, window->window, x, y); +} + + +vec2 platform_get_window_size(platform_window *window) +{ + vec2 res; + res.x = window->width; + res.y = window->height; + return res; +} + +platform_window platform_open_window(char *name, u16 width, u16 height, u16 max_w, u16 max_h, u16 min_w, u16 min_h) +{ + bool has_max_size = max_w || max_h; + + platform_window window; + window.has_focus = true; + window.curr_cursor_type = CURSOR_DEFAULT; + window.next_cursor_type = CURSOR_DEFAULT; + window.clipboard_str = 0; + window.clipboard_strlen = 0; + + static int att[] = + { + GLX_X_RENDERABLE , True, + GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, + GLX_RENDER_TYPE , GLX_RGBA_BIT, + GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR, + GLX_RED_SIZE , 8, + GLX_GREEN_SIZE , 8, + GLX_BLUE_SIZE , 8, + GLX_ALPHA_SIZE , 8, + GLX_DEPTH_SIZE , 24, + GLX_STENCIL_SIZE , 8, + GLX_DOUBLEBUFFER , True, + //GLX_SAMPLE_BUFFERS , 1, + //GLX_SAMPLES , 4, + None + }; + + window.display = XOpenDisplay(NULL); + + if(window.display == NULL) { + return window; + } + + window.parent = DefaultRootWindow(window.display); + + int fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(window.display, DefaultScreen(window.display), att, &fbcount); + int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999; + + int i; + for (i=0; i<fbcount; ++i) + { + XVisualInfo *vi = glXGetVisualFromFBConfig(window.display, fbc[i] ); + if ( vi ) + { + int samp_buf, samples; + glXGetFBConfigAttrib(window.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf ); + glXGetFBConfigAttrib(window.display, fbc[i], GLX_SAMPLES , &samples ); + + if ( best_fbc < 0 || (samp_buf && samples > best_num_samp)) + best_fbc = i, best_num_samp = samples; + if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp ) + worst_fbc = i, worst_num_samp = samples; + } + XFree(vi); + } + + GLXFBConfig bestFbc = fbc[best_fbc]; + XFree(fbc); + + XVisualInfo *vi = glXGetVisualFromFBConfig(window.display, bestFbc ); + window.visual_info = vi; + + if(window.visual_info == NULL) { + return window; + } + + window.cmap = XCreateColormap(window.display, window.parent, window.visual_info->visual, AllocNone); + + // calculate window center + XRRScreenResources *screens = XRRGetScreenResources(window.display, window.parent); + XRRCrtcInfo *info = XRRGetCrtcInfo(window.display, screens, screens->crtcs[0]); + + s32 center_x = (info->width / 2) - (width / 2); + s32 center_y = (info->height / 2) - (height / 2); + + XRRFreeCrtcInfo(info); + XRRFreeScreenResources(screens); + + XSetWindowAttributes window_attributes; + window_attributes.colormap = window.cmap; + window_attributes.border_pixel = 0; + window_attributes.event_mask = KeyPressMask | KeyReleaseMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask; + + window.window = XCreateWindow(window.display, window.parent, center_x, center_y, width, height, 0, window.visual_info->depth, InputOutput, window.visual_info->visual, CWColormap | CWEventMask | CWBorderPixel, &window_attributes); + + XMapWindow(window.display, window.window); + XFlush(window.display); + + XSync(window.display, False); + + XSizeHints hints; + + if (has_max_size) + hints.flags = PMaxSize | PMinSize | USPosition; + else + hints.flags = PMinSize | USPosition; + hints.x = center_x; + hints.y = center_y; + hints.max_width = width; + hints.max_height = height; + hints.min_width = min_w; + hints.min_height = min_h; + + XSetWMNormalHints(window.display, window.window, &hints); + + // window name + { + Atom WM_NAME = XInternAtom(window.display, "WM_NAME", False); + Atom _NET_WM_NAME = XInternAtom(window.display, "_NET_WM_NAME", False); + Atom _NET_WM_ICON_NAME = XInternAtom(window.display, "_NET_WM_ICON_NAME", False); + + char *list[1] = { (char *) name }; + XTextProperty property; + + XStoreName(window.display, window.window, name); + + Xutf8TextListToTextProperty(window.display, list, 1, XUTF8StringStyle, + &property); + XSetTextProperty(window.display, window.window, &property, WM_NAME); + XSetTextProperty(window.display, window.window, &property, _NET_WM_NAME); + XSetTextProperty(window.display, window.window, &property, XA_WM_NAME); + XSetTextProperty(window.display, window.window, &property, _NET_WM_ICON_NAME); + XFree(property.value); + + XClassHint class_hint; + class_hint.res_name = name; + class_hint.res_class = name; + XSetClassHint(window.display, window.window, &class_hint); + } + + + // hide taskbar icon and stay on top +#if 0 + { + Atom wm_state = XInternAtom(window.display, + "_NET_WM_STATE", False); + Atom taskbar_atom = XInternAtom(window.display, + "_NET_WM_STATE_SKIP_TASKBAR", False); + Atom above_atom = XInternAtom(window.display, + "_NET_WM_STATE_ABOVE", False); + + Atom atom_list[2] = {taskbar_atom, above_atom}; + XChangeProperty(window.display, window.window, wm_state, XA_ATOM, 32, + PropModeReplace, (const unsigned char*)&atom_list, 2); + } +#endif + + { + XWMHints* win_hints = XAllocWMHints(); + win_hints->flags = StateHint | IconPositionHint; + win_hints->initial_state = IconicState; + win_hints->icon_x = 0; + win_hints->icon_y = 0; + + /* pass the hints to the window manager. */ + XSetWMHints(window.display, window.window, win_hints); + XFree(win_hints); + } + + static GLXContext share_list = 0; + + // get opengl context + window.gl_context = glXCreateContext(window.display, window.visual_info, + share_list, GL_TRUE); + + if (share_list == 0) + share_list = window.gl_context; + glXMakeCurrent(window.display, window.window, window.gl_context); + + // blending + glEnable(GL_DEPTH_TEST); + //glDepthMask(true); + //glClearDepth(50); + glDepthFunc(GL_LEQUAL); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // setup multisampling +#if 0 + glEnable(GL_ALPHA_TEST); + glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); + glEnable(GL_SAMPLE_ALPHA_TO_ONE); + glEnable(GL_MULTISAMPLE); + glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST); +#endif + + window.is_open = true; + window.width = width; + window.height = height; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 1); + + glMatrixMode(GL_MODELVIEW); + + create_key_tables(window); + + // recieve window close event + window.quit = XInternAtom(window.display, "WM_DELETE_WINDOW", False); + + GET_ATOM(XdndEnter); + GET_ATOM(XdndPosition); + GET_ATOM(XdndStatus); + GET_ATOM(XdndTypeList); + GET_ATOM(XdndActionCopy); + GET_ATOM(XdndDrop); + GET_ATOM(XdndFinished); + GET_ATOM(XdndSelection); + GET_ATOM(XdndLeave); + GET_ATOM(PRIMARY); + GET_ATOM(CLIPBOARD); + GET_ATOM(UTF8_STRING); + GET_ATOM(COMPOUND_STRING); + GET_ATOM(TARGETS); + GET_ATOM(MULTIPLE); + GET_ATOM(_NET_WM_STATE); + + array atoms = array_create(sizeof(Atom)); + array_push(&atoms, &window.quit); + array_push(&atoms, &window.XdndEnter); + array_push(&atoms, &window.XdndPosition); + array_push(&atoms, &window.XdndStatus); + array_push(&atoms, &window.XdndTypeList); + array_push(&atoms, &window.XdndActionCopy); + array_push(&atoms, &window.XdndDrop); + array_push(&atoms, &window.XdndFinished); + array_push(&atoms, &window.XdndSelection); + array_push(&atoms, &window.XdndLeave); + array_push(&atoms, &window.PRIMARY); + array_push(&atoms, &window.CLIPBOARD); + array_push(&atoms, &window.UTF8_STRING); + array_push(&atoms, &window.COMPOUND_STRING); + array_push(&atoms, &window.TARGETS); + array_push(&atoms, &window.MULTIPLE); + array_push(&atoms, &window._NET_WM_STATE); + + XSetWMProtocols(window.display, window.window, atoms.data, atoms.length); + array_destroy(&atoms); + + Atom XdndAware = XInternAtom(window.display, "XdndAware", False); + Atom xdnd_version = 5; + XChangeProperty(window.display, window.window, XdndAware, XA_ATOM, 32, + PropModeReplace, (unsigned char*)&xdnd_version, 1); + + + XFlush(window.display); + XSync(window.display, True); + + return window; +} + +inline bool platform_window_is_valid(platform_window *window) +{ + return window->window && window->display; +} + +void platform_destroy_window(platform_window *window) +{ + glXMakeCurrent(window->display, None, NULL); + glXDestroyContext(window->display, window->gl_context); + XDestroyWindow(window->display, window->window); + XCloseDisplay(window->display); + XFree(window->visual_info); + mem_free(window->clipboard_str); + + window->window = 0; + window->display = 0; +} + +void platform_hide_window_taskbar_icon(platform_window *window) +{ + XClientMessageEvent m; + memset(&m, 0, sizeof(XClientMessageEvent)); + m.type = ClientMessage; + m.display = window->display; + m.window = window->window; + m.message_type = window->_NET_WM_STATE; + m.format=32; + m.data.l[0] = 1; + m.data.l[1] = XInternAtom(window->display, "_NET_WM_STATE_SKIP_TASKBAR", False); + m.data.l[2] = None; + m.data.l[3] = 1; + m.data.l[4] = 0; + XSendEvent(window->display, window->window, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&m); + + XFlush(window->display); +} + +void platform_handle_events(platform_window *window, mouse_input *mouse, keyboard_input *keyboard) +{ + mouse->left_state &= ~MOUSE_CLICK; + mouse->right_state &= ~MOUSE_CLICK; + mouse->left_state &= ~MOUSE_DOUBLE_CLICK; + mouse->right_state &= ~MOUSE_DOUBLE_CLICK; + mouse->left_state &= ~MOUSE_RELEASE; + mouse->right_state &= ~MOUSE_RELEASE; + memset(keyboard->input_keys, 0, MAX_KEYCODE); + mouse->move_x = 0; + mouse->move_y = 0; + mouse->scroll_state = 0; + keyboard->text_changed = false; + + XClientMessageEvent m; + + s32 pending_events = XPending(window->display); + for (s32 i = 0; i < pending_events; i++) + { + XNextEvent(window->display, &window->event); + if (window->event.type == ClientMessage) + { + static int xdnd_version=0; + + if ((Atom)window->event.xclient.data.l[0] == window->quit) { + window->is_open = false; + } + + if (window->event.xclient.message_type == window->XdndDrop) + { + if (window->xdnd_req == None) { + /* say again - not interested! */ + memset(&m, 0, sizeof(XClientMessageEvent)); + m.type = ClientMessage; + m.display = window->event.xclient.display; + m.window = window->event.xclient.data.l[0]; + m.message_type = window->XdndFinished; + m.format=32; + m.data.l[0] = window->window; + m.data.l[1] = 0; + m.data.l[2] = None; /* fail! */ + XSendEvent(window->display, window->event.xclient.data.l[0], False, NoEventMask, (XEvent*)&m); + } else { + /* convert */ + if(xdnd_version >= 1) { + XConvertSelection(window->display, window->XdndSelection, window->xdnd_req, window->PRIMARY, window->window, window->event.xclient.data.l[2]); + } else { + printf("time to find the time.\n"); + //XConvertSelection(window->display, window->XdndSelection, window->xdnd_req, window->PRIMARY, window->xwindow, CurrentTime); + } + } + } + } + else if (window->event.type == LeaveNotify) + { + mouse->x = MOUSE_OFFSCREEN; + mouse->y = MOUSE_OFFSCREEN; + } + else if (window->event.type == ConfigureNotify) + { + XConfigureEvent xce = window->event.xconfigure; + window->width = xce.width; + window->height = xce.height; + glViewport(0, 0, window->width, window->height); + } + else if (window->event.type == FocusIn) + { + window->has_focus = true; + } + else if (window->event.type == FocusOut) + { + mouse->x = MOUSE_OFFSCREEN; + mouse->y = MOUSE_OFFSCREEN; + window->has_focus = false; + + // if windows loses focus, set all keys to not pressed + memset(keyboard->keys, 0, MAX_KEYCODE); + } + else if (window->event.type == MotionNotify) + { + s32 x = mouse->x; + s32 y = mouse->y; + + mouse->total_move_x += window->event.xmotion.x - mouse->x; + mouse->total_move_y += window->event.xmotion.y - mouse->y; + + mouse->x = window->event.xmotion.x; + mouse->y = window->event.xmotion.y; + + mouse->move_x = mouse->x - x; + mouse->move_y = mouse->y - y; + } + else if (window->event.type == ButtonPress) + { + Time ev_time = window->event.xbutton.time; + static Time last_ev_time = 0; + + bool is_left_down = window->event.xbutton.button == Button1; + bool is_right_down = window->event.xbutton.button == Button3; + bool is_middle_down = window->event.xbutton.button == Button2; + bool scroll_up = window->event.xbutton.button == Button4; + bool scroll_down = window->event.xbutton.button == Button5; + + if (scroll_up) + mouse->scroll_state = SCROLL_UP; + if (scroll_down) + mouse->scroll_state = SCROLL_DOWN; + + if (is_left_down) + { + if (ev_time - last_ev_time < 200) + { + mouse->left_state |= MOUSE_DOUBLE_CLICK; + } + + mouse->left_state |= MOUSE_DOWN; + mouse->left_state |= MOUSE_CLICK; + + mouse->total_move_x = 0; + mouse->total_move_y = 0; + last_ev_time = ev_time; + } + if (is_right_down) + { + mouse->right_state |= MOUSE_DOWN; + mouse->right_state |= MOUSE_CLICK; + } + } + else if (window->event.type == ButtonRelease) + { + bool is_left_up = window->event.xbutton.button == Button1; + bool is_right_up = window->event.xbutton.button == Button3; + bool is_middle_up = window->event.xbutton.button == Button2; + + if (is_left_up) + { + mouse->left_state = MOUSE_RELEASE; + } + if (is_right_up) + { + mouse->right_state = MOUSE_RELEASE; + } + } + else if(window->event.type == KeyPress) + { + s32 key = window->event.xkey.keycode; + + keyboard->keys[keycode_map[key]] = true; + keyboard->input_keys[keycode_map[key]] = true; + + // https://gist.github.com/rickyzhang82/8581a762c9f9fc6ddb8390872552c250 + //printf("state: %d\n", window->event.xkey.state); + + // remove key control key from mask so it doesnt block input + window->event.xkey.state &= ~ControlMask; + + // replace capslock with shiftkey else keylookup returns 0... + if (window->event.xkey.state == 2) + window->event.xkey.state = 1; + + KeySym ksym = XLookupKeysym(&window->event.xkey, window->event.xkey.state); + + if (keyboard->take_input) + { + char *ch = 0; + switch(ksym) + { + case XK_space: ch = " "; break; + case XK_exclam: ch = "!"; break; + case XK_quotedbl: ch = "\""; break; + case XK_numbersign: ch = "#"; break; + case XK_dollar: ch = "$"; break; + case XK_percent: ch = "%"; break; + case XK_ampersand: ch = "&"; break; + case XK_apostrophe: ch = "`"; break; + case XK_parenleft: ch = "("; break; + case XK_parenright: ch = ")"; break; + case XK_asterisk: ch = "*"; break; + case XK_plus: ch = "+"; break; + case XK_comma: ch = ","; break; + case XK_minus: ch = "-"; break; + case XK_period: ch = "."; break; + case XK_slash: ch = "/"; break; + case XK_0: ch = "0"; break; + case XK_1: ch = "1"; break; + case XK_2: ch = "2"; break; + case XK_3: ch = "3"; break; + case XK_4: ch = "4"; break; + case XK_5: ch = "5"; break; + case XK_6: ch = "6"; break; + case XK_7: ch = "7"; break; + case XK_8: ch = "8"; break; + case XK_9: ch = "9"; break; + + case XK_colon: ch = ":"; break; + case XK_semicolon: ch = ";"; break; + case XK_less: ch = "<"; break; + case XK_equal: ch = "="; break; + case XK_greater: ch = ">"; break; + case XK_question: ch = "?"; break; + case XK_at: ch = "@"; break; + case XK_bracketleft: ch = "["; break; + case XK_backslash: ch = "\\"; break; + case XK_bracketright: ch = "]"; break; + case XK_asciicircum: ch = "^"; break; + case XK_underscore: ch = "_"; break; + case XK_grave: ch = "`"; break; + case XK_braceleft: ch = "{"; break; + case XK_bar: ch = "|"; break; + case XK_braceright: ch = "}"; break; + case XK_asciitilde: ch = "~"; break; + } + + if ((ksym >= XK_A && ksym <= XK_Z) || (ksym >= XK_a && ksym <= XK_z)) + { + ch = XKeysymToString(ksym); + } + + if (ch && keyboard->input_mode == INPUT_NUMERIC) + { + if (!(*ch >= 48 && *ch <= 57)) + { + ch = 0; + } + } + + keyboard_handle_input_string(window, keyboard, ch); + } + } + else if (window->event.type == KeyRelease) + { + s32 key = window->event.xkey.keycode; + keyboard->keys[keycode_map[key]] = false; + + KeySym ksym = XLookupKeysym(&window->event.xkey, 0); + } + else if (window->event.type == SelectionClear) + { + window->clipboard_str = 0; + window->clipboard_strlen = 0; + } + else if (window->event.type == SelectionRequest) + { + Atom formats[] = {window->UTF8_STRING, window->COMPOUND_STRING, XA_STRING}; + Atom targets[] = {window->TARGETS, window->MULTIPLE, window->UTF8_STRING, window->COMPOUND_STRING, XA_STRING}; + int formatCount = sizeof(formats) / sizeof(formats[0]); + + XSelectionEvent event = {.type = SelectionNotify, .selection = window->event.xselectionrequest.selection, .target = window->event.xselectionrequest.target, .display = window->event.xselectionrequest.display, .requestor = window->event.xselectionrequest.requestor, .time = window->event.xselectionrequest.time}; + + if(window->event.xselectionrequest.target == window->TARGETS) { + XChangeProperty(window->display, window->event.xselectionrequest.requestor, window->event.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, sizeof(targets) / sizeof(targets[0])); + + event.property = window->event.xselectionrequest.property; + } else { + event.property = None; + int i; + for(i = 0; i < formatCount; i++) { + if(window->event.xselectionrequest.target == formats[i]) { + XChangeProperty(window->display, window->event.xselectionrequest.requestor, window->event.xselectionrequest.property, window->event.xselectionrequest.target, 8, PropModeReplace, (unsigned char*)window->clipboard_str, window->clipboard_strlen); + + event.property = window->event.xselectionrequest.property; + break; + } + } + } + + XSendEvent(window->display, window->event.xselectionrequest.requestor, False, 0, (XEvent*)&event); + XFlush(window->display); + } + } +} + +inline void platform_show_alert(char *title, char *message) +{ + char command[MAX_INPUT_LENGTH]; + snprintf(command, MAX_INPUT_LENGTH, "notify-send \"%s\" \"%s\"", title, message); + platform_run_command(command); +} + +inline void platform_window_swap_buffers(platform_window *window) +{ + // set cursor if changed + if (window->curr_cursor_type != window->next_cursor_type) + { + int cursor_shape = 0; + switch(window->next_cursor_type) + { + case CURSOR_DEFAULT: cursor_shape = XC_arrow; break; + case CURSOR_POINTER: cursor_shape = XC_hand1; break; + } + Cursor cursor = XCreateFontCursor(window->display, cursor_shape); + XDefineCursor(window->display, window->window, cursor); + window->curr_cursor_type = window->next_cursor_type; + } + + glXSwapBuffers(window->display, window->window); +} + +u64 platform_get_time(time_type time_type, time_precision precision) +{ + s32 type = CLOCK_REALTIME; + switch(time_type) + { + case TIME_FULL: type = CLOCK_REALTIME; break; + case TIME_THREAD: type = CLOCK_THREAD_CPUTIME_ID; break; + case TIME_PROCESS: type = CLOCK_PROCESS_CPUTIME_ID; break; + } + + struct timespec tms; + if (clock_gettime(type,&tms)) { + return -1; + } + + long result = 0; + + if (precision == TIME_NS) + { + result = tms.tv_sec * 1000000000; + result += tms.tv_nsec; + if (tms.tv_nsec % 1000 >= 500) { + ++result; + } + } + else if (precision == TIME_US) + { + result = tms.tv_sec * 1000000; + result += tms.tv_nsec/1000; + if (tms.tv_nsec % 1000 >= 500) { + ++result; + } + } + else if (precision == TIME_MILI_S) + { + result = tms.tv_sec * 1000; + result += tms.tv_nsec/1000000; + if (tms.tv_nsec % 1000 >= 500) { + ++result; + } + } + else if (precision == TIME_S) + { + result = tms.tv_sec; + result += tms.tv_nsec/1000000000; + if (tms.tv_nsec % 1000 >= 500) { + ++result; + } + } + + return result; +} + +inline s32 platform_get_cpu_count() +{ + return (int)sysconf(_SC_NPROCESSORS_ONLN); +} + +inline s32 platform_get_memory_size() +{ + uint64_t aid = (uint64_t) sysconf(_SC_PHYS_PAGES); + aid *= (uint64_t) sysconf(_SC_PAGESIZE); + aid /= (uint64_t) (1024 * 1024); + return (int)(aid); +} + +void platform_show_message(platform_window *window, char *message, char *title) +{ + char command[MAX_INPUT_LENGTH]; + snprintf(command, MAX_INPUT_LENGTH, "zenity --info --text=\"%s\" --title=\"%s\" --width=240", message, title); + FILE *f = popen(command, "r"); +} + +static void* platform_open_file_dialog_thread(void *data) +{ + struct open_dialog_args *args = data; + + FILE *f; + + char current_val[MAX_INPUT_LENGTH]; + string_copyn(current_val, args->buffer, MAX_INPUT_LENGTH); + + char file_filter[MAX_INPUT_LENGTH]; + file_filter[0] = 0; + if (args->file_filter) + snprintf(file_filter, MAX_INPUT_LENGTH, "--file-filter=\"%s\"", args->file_filter); + + char start_path[MAX_INPUT_LENGTH]; + start_path[0] = 0; + if (args->start_path) + snprintf(start_path, MAX_INPUT_LENGTH, "--filename=\"%s\"", args->start_path); + + + char command[MAX_INPUT_LENGTH]; + + if (args->type == OPEN_FILE) + { + snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection %s %s", file_filter, start_path); + } + else if (args->type == OPEN_DIRECTORY) + { + snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection --directory %s %s", file_filter, start_path); + } + else if (args->type == SAVE_FILE) + { + snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection --save --confirm-overwrite %s %s", file_filter, start_path); + } + + f = popen(command, "r"); + + char buffer[MAX_INPUT_LENGTH]; + char *result = fgets(buffer, MAX_INPUT_LENGTH, f); + + if (!result) + return 0; + + // replace newlines with 0, we only want one file path + s32 len = strlen(buffer); + for (s32 x = 0; x < len; x++) + { + if (buffer[x] == '\n') buffer[x] = 0; + } + + if (strcmp(buffer, current_val) != 0 && strcmp(buffer, "") != 0) + { + string_copyn(args->buffer, buffer, MAX_INPUT_LENGTH); + s32 len = strlen(args->buffer); + args->buffer[len] = 0; + } + + return 0; +} + +void *platform_open_file_dialog_block(void *arg) +{ + thread thr = thread_start(platform_open_file_dialog_thread, arg); + thread_join(&thr); + mem_free(arg); + return 0; +} + +void platform_list_files_block(array *list, char *start_dir, array filters, bool recursive, memory_bucket *bucket, bool include_directories, bool *is_cancelled) +{ + assert(list); + + s32 len = 0; + char *matched_filter = 0; + + char *subdirname_buf; + if (bucket) + subdirname_buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + subdirname_buf = mem_alloc(MAX_INPUT_LENGTH); + + DIR *d; + struct dirent *dir; + d = opendir(start_dir); + if (d) { + set_active_directory(start_dir); + while ((dir = readdir(d)) != NULL) { + if (*is_cancelled) break; + set_active_directory(start_dir); + + if (dir->d_type == DT_DIR) + { + if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) + continue; + + if (include_directories) + { + if ((len = filter_matches(&filters, dir->d_name, + &matched_filter)) && len != -1) + { + char *buf; + if (bucket) + buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + buf = mem_alloc(MAX_INPUT_LENGTH); + + //realpath(dir->d_name, buf); + snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, dir->d_name); + + found_file f; + f.path = buf; + + if (bucket) + f.matched_filter = memory_bucket_reserve(bucket, len+1); + else + f.matched_filter = mem_alloc(len+1); + + string_copyn(f.matched_filter, matched_filter, len+1); + + mutex_lock(&list->mutex); + array_push_size(list, &f, sizeof(found_file)); + mutex_unlock(&list->mutex); + } + } + + if (recursive) + { + string_copyn(subdirname_buf, start_dir, MAX_INPUT_LENGTH); + string_appendn(subdirname_buf, dir->d_name, MAX_INPUT_LENGTH); + string_appendn(subdirname_buf, "/", MAX_INPUT_LENGTH); + + // do recursive search + platform_list_files_block(list, subdirname_buf, filters, recursive, bucket, include_directories, is_cancelled); + } + } + // we handle DT_UNKNOWN for file systems that do not support type lookup. + else if (dir->d_type == DT_REG || dir->d_type == DT_UNKNOWN) + { + // check if name matches pattern + if ((len = filter_matches(&filters, dir->d_name, + &matched_filter)) && len != -1) + { + char *buf; + if (bucket) + buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + buf = mem_alloc(MAX_INPUT_LENGTH); + + //realpath(dir->d_name, buf); + snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, dir->d_name); + + found_file f; + f.path = buf; + + if (bucket) + f.matched_filter = memory_bucket_reserve(bucket, len+1); + else + f.matched_filter = mem_alloc(len+1); + + string_copyn(f.matched_filter, matched_filter, len+1); + + mutex_lock(&list->mutex); + array_push_size(list, &f, sizeof(found_file)); + mutex_unlock(&list->mutex); + + } + } + } + closedir(d); + } + + if (!bucket) + mem_free(subdirname_buf); +} + +char *platform_get_full_path(char *file) +{ + char *buf = mem_alloc(PATH_MAX); + buf[0] = 0; + + char *result = realpath(file, buf); + + if (!result) + { + buf[0] = 0; + return buf; + } + + return buf; +} + +inline u64 string_to_u64(char *str) +{ + return (u64)strtoull(str, 0, 10); +} + +inline u32 string_to_u32(char *str) +{ + return (u32)strtoul(str, 0, 10); +} + +inline u16 string_to_u16(char *str) +{ + return (u16)strtoul(str, 0, 10); +} + +inline u8 string_to_u8(char *str) +{ + return (u8)strtoul(str, 0, 10); +} + +inline s64 string_to_s64(char *str) +{ + return (s64)strtoll(str, 0, 10); +} + +inline s32 string_to_s32(char *str) +{ + return (u32)strtol(str, 0, 10); +} + +inline s16 string_to_s16(char *str) +{ + return (s16)strtol(str, 0, 10); +} + +inline s8 string_to_s8(char *str) +{ + return (s8)strtol(str, 0, 10); +} + +inline void platform_open_url(char *url) +{ + char buffer[MAX_INPUT_LENGTH]; + snprintf(buffer, MAX_INPUT_LENGTH, "xdg-open %s", url); + platform_run_command(buffer); +} + +inline void platform_run_command(char *command) +{ + s32 result = system(command); +} + +void platform_set_icon(platform_window *window, image *img) +{ + s32 w = img->width; + s32 h = img->height; + + s32 nelements = (w * h) + 2; + + unsigned long data[nelements]; + int i = 0; + (data)[i++] = w; + (data)[i++] = h; + + for (s32 y = 0; y < h; y++) + { + for (s32 x = 0; x < w; x++) + { + s32 *pixel = (s32*)(&((data)[i++])); + + s32 img_pixel = *(((s32*)img->data+(x+(y*w)))); + + // 0xAABBGGRR + s32 a = (img_pixel>>24) & 0x000000FF; + s32 b = (img_pixel>>16) & 0x000000FF; + s32 g = (img_pixel>> 8) & 0x000000FF; + s32 r = (img_pixel>> 0) & 0x000000FF; + + //s32 c = (r << 24) | (g << 16) | (b << 8) | (a << 0); + s32 c = (a << 24) | (r << 16) | (g << 8) | (b << 0); + *pixel = c; + } + } + + Atom property = XInternAtom(window->display, "_NET_WM_ICON", 0); + Atom cardinal = XInternAtom(window->display, "CARDINAL", False); + + int result = XChangeProperty(window->display, window->window, + property, cardinal, 32, PropModeReplace, + (unsigned char *)data, nelements); +} diff --git a/src/linux/thread.c b/src/linux/thread.c new file mode 100644 index 0000000..b6295ef --- /dev/null +++ b/src/linux/thread.c @@ -0,0 +1,129 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +// stop gcc from reporting implicit declaration warning.. +extern long int syscall (long int __sysno, ...); +extern int pthread_tryjoin_np(pthread_t thread, void **retval); + +thread thread_start(void *(*start_routine) (void *), void *arg) +{ + thread result; + result.valid = false; + + pthread_attr_t attr; + int attr_init_result = pthread_attr_init(&attr); + if (attr_init_result) + return result; + + int start_thread_result = pthread_create(&result.thread, &attr, start_routine, arg); + if (start_thread_result) + { + pthread_attr_destroy(&attr); + return result; + } + + result.valid = true; + pthread_attr_destroy(&attr); + + return result; +} + +inline void thread_detach(thread *thread) +{ + if (thread->valid) + { + pthread_detach(thread->thread); + } +} + +inline void thread_join(thread *thread) +{ + if (thread->valid) + { + void *retval; + pthread_join(thread->thread, &retval); + } +} + +bool thread_tryjoin(thread *thread) +{ + if (thread->valid) + { + void *retval; + bool thread_joined = !pthread_tryjoin_np(thread->thread, &retval); + return thread_joined; + } + return false; +} + +inline void thread_stop(thread *thread) +{ + if (thread->valid) + { + pthread_cancel(thread->thread); + } +} + +mutex mutex_create() +{ + mutex result; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); + + pthread_mutex_init(&result.mutex, &attr); + + pthread_mutexattr_destroy(&attr); + + return result; +} + +mutex mutex_create_recursive() +{ + mutex result; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&result.mutex, &attr); + + pthread_mutexattr_destroy(&attr); + + return result; +} + +inline void mutex_lock(mutex *mutex) +{ + pthread_mutex_lock(&mutex->mutex); +} + +inline bool mutex_trylock(mutex *mutex) +{ + return !pthread_mutex_trylock(&mutex->mutex); +} + +inline void mutex_unlock(mutex *mutex) +{ + pthread_mutex_unlock(&mutex->mutex); +} + +inline void mutex_destroy(mutex *mutex) +{ + mutex_unlock(mutex); + pthread_mutex_destroy(&mutex->mutex); +} + +inline u32 thread_get_id() +{ + return (u32)syscall(__NR_gettid); +} + +inline void thread_sleep(u64 microseconds) +{ + usleep(microseconds); +} diff --git a/src/localization.c b/src/localization.c new file mode 100644 index 0000000..41b1fcf --- /dev/null +++ b/src/localization.c @@ -0,0 +1,149 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +mo_file load_localization_file(u8 *start_addr, u8 *end_addr, u8 *img_start, u8 *img_end, + char *locale_id, char *locale_name) +{ + mo_file mo; + mo.translations = array_create(sizeof(mo_translation)); + + { + mo.header = *(mo_header*)start_addr; + mo.locale_id = mem_alloc(strlen(locale_id)+1); + string_copyn(mo.locale_id, locale_id, strlen(locale_id)+1); + + mo.locale_full = mem_alloc(strlen(locale_name)+1); + string_copyn(mo.locale_full, locale_name, strlen(locale_name)+1); + + mo.icon = assets_load_image(img_start, img_end, false); + + char *buffer = (char*)start_addr; + mo_entry *identifiers = (mo_entry*)(buffer + mo.header.identifier_table_offset); + mo_entry *translations = (mo_entry*)(buffer + mo.header.translation_table_offset); + + // skip first one because this is file information + for (s32 i = 1; i < mo.header.number_of_strings; i++) + { + mo_entry *entry = &identifiers[i]; + mo_entry *trans = &translations[i]; + + mo_translation translation; + translation.identifier_len = entry->length; + translation.identifier = buffer+entry->offset; + translation.translation = buffer+trans->offset; + + array_push(&mo.translations, &translation); + //printf("%s=%s\n", translation.identifier, translation.translation); + } + } + + return mo; +} + +char* locale_get_name() +{ + if (!global_localization.active_localization) + { + return "[NO LOCALE]"; + } + + return global_localization.active_localization->locale_full; +} + +char* locale_get_id() +{ + if (!global_localization.active_localization) + { + return "[NO LOCALE]"; + } + + return global_localization.active_localization->locale_id; +} + +bool set_locale(char *country_id) +{ + if (country_id == 0 && global_localization.mo_files.length) + { + global_localization.active_localization = array_at(&global_localization.mo_files, 0); + return true; + } + + for (s32 i = 0; i < global_localization.mo_files.length; i++) + { + mo_file *file = array_at(&global_localization.mo_files, i); + if (strcmp(file->locale_id, country_id) == 0) + { + global_localization.active_localization = file; + return true; + } + } + + // if localization is not found, default to first in list (english), return false to report error + if (global_localization.mo_files.length) + global_localization.active_localization = array_at(&global_localization.mo_files, 0); + else + global_localization.active_localization = 0; + + return false; +} + +char* localize(const char *identifier) +{ + if (!global_localization.active_localization) + { + //printf("NO LOCALE SELECTED."); + return (char*)identifier; + } + + s32 len = strlen(identifier); + for (s32 i = 0; i < global_localization.active_localization->translations.length; i++) + { + mo_translation *trans = array_at(&global_localization.active_localization->translations, i); + + if (trans->identifier_len == len && strcmp(identifier, trans->identifier) == 0) + { + return trans->translation; + } + } + printf("MISSING TRANSLATION: [%s][%s]\n", identifier, global_localization.active_localization->locale_id); + return "MISSING"; +} + +void load_available_localizations() +{ + global_localization.mo_files = array_create(sizeof(mo_file)); + array_reserve(&global_localization.mo_files, 10); + + mo_file en = load_localization_file(_binary____data_translations_en_English_mo_start, + _binary____data_translations_en_English_mo_end, + _binary____data_imgs_en_png_start, + _binary____data_imgs_en_png_end, + "en", "English"); + + mo_file nl = load_localization_file(_binary____data_translations_nl_Dutch_mo_start, + _binary____data_translations_nl_Dutch_mo_end, + _binary____data_imgs_nl_png_start, + _binary____data_imgs_nl_png_end, + "nl", "Dutch"); + + array_push(&global_localization.mo_files, &en); + array_push(&global_localization.mo_files, &nl); +} + +void destroy_available_localizations() +{ + for (s32 i = 0; i < global_localization.mo_files.length; i++) + { + mo_file *file = array_at(&global_localization.mo_files, i); + array_destroy(&file->translations); + mem_free(file->locale_id); + mem_free(file->locale_full); + + if (file->icon) + assets_destroy_image(file->icon); + } + array_destroy(&global_localization.mo_files); +} diff --git a/src/localization.h b/src/localization.h new file mode 100644 index 0000000..4a35bf1 --- /dev/null +++ b/src/localization.h @@ -0,0 +1,59 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_LOCALIZATION +#define INCLUDE_LOCALIZATION + +// https://www.science.co.il/language/Locale-codes.php + +typedef struct t_mo_entry +{ + s32 length; + s32 offset; +} mo_entry; + +typedef struct t_mo_translation +{ + s32 identifier_len; + char *identifier; + char *translation; +} mo_translation; + +typedef struct t_mo_header +{ + s32 magic_number; + s32 file_format_revision; + s32 number_of_strings; + s32 identifier_table_offset; + s32 translation_table_offset; + s32 hashtable_size; + s32 hashtable_offset; +} mo_header; + +typedef struct t_mo_file +{ + mo_header header; + array translations; + char *locale_id; + char *locale_full; + image *icon; +} mo_file; + +typedef struct t_localization +{ + array mo_files; + mo_file *active_localization; +} localization; + +localization global_localization; + +char* locale_get_id(); +char* locale_get_name(); +char* localize(const char *identifier); +bool set_locale(char *country_id); +void load_available_localizations(); + +#endif
\ No newline at end of file diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..f63edef --- /dev/null +++ b/src/memory.h @@ -0,0 +1,19 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_MEMORY +#define INCLUDE_MEMORY + +#define mem_alloc(size) malloc(size) +#define mem_free(p) free(p) +#define mem_realloc(p, size) realloc(p, size) +#define memory_print_leaks() {} + +#define STBI_MALLOC(sz) mem_alloc(sz) +#define STBI_REALLOC(p, newsz) mem_realloc(p, newsz) +#define STBI_FREE(p) mem_free(p) + +#endif
\ No newline at end of file diff --git a/src/memory_bucket.c b/src/memory_bucket.c new file mode 100644 index 0000000..bc9f7e9 --- /dev/null +++ b/src/memory_bucket.c @@ -0,0 +1,74 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +inline memory_bucket memory_bucket_init(s32 bucket_size) +{ + assert(bucket_size >= MAX_INPUT_LENGTH); + + memory_bucket collection; + collection.bucket_mutex = mutex_create(); + collection.buckets = array_create(sizeof(memory_bucket_entry)); + + memory_bucket_entry bucket; + bucket.data = mem_alloc(bucket_size); + bucket.length = bucket_size; + bucket.cursor = 0; + array_push(&collection.buckets, &bucket); + return collection; +} + +void* memory_bucket_reserve(memory_bucket *bucket, s32 reserve_length) +{ + mutex_lock(&bucket->bucket_mutex); + memory_bucket_entry *bucket_entry = 0; + for (s32 i = 0; i < bucket->buckets.length; i++) + { + bucket_entry = array_at(&bucket->buckets, i); + + if (bucket_entry->length - bucket_entry->cursor < reserve_length) continue; + + void *space = bucket_entry->data+bucket_entry->cursor; + bucket_entry->cursor += reserve_length; + mutex_unlock(&bucket->bucket_mutex); + + return space; + } + + // failed to find suitable space, allocate new bucket + memory_bucket_entry new_bucket; + new_bucket.data = mem_alloc(bucket_entry->length); + new_bucket.length = bucket_entry->length; + new_bucket.cursor = 0; + array_push(&bucket->buckets, &new_bucket); + mutex_unlock(&bucket->bucket_mutex); + + return new_bucket.data; +} + +inline void memory_bucket_reset(memory_bucket *bucket) +{ + mutex_lock(&bucket->bucket_mutex); + for (s32 i = 0; i < bucket->buckets.length; i++) + { + memory_bucket_entry *bucket_entry = array_at(&bucket->buckets, i); + bucket_entry->cursor = 0; + } + mutex_unlock(&bucket->bucket_mutex); +} + +inline void memory_bucket_destroy(memory_bucket *bucket) +{ + mutex_lock(&bucket->bucket_mutex); + for (s32 i = 0; i < bucket->buckets.length; i++) + { + memory_bucket_entry *bucket_entry = array_at(&bucket->buckets, i); + mem_free(bucket_entry->data); + } + array_destroy(&bucket->buckets); + mutex_unlock(&bucket->bucket_mutex); + + mutex_destroy(&bucket->bucket_mutex); +}
\ No newline at end of file diff --git a/src/memory_bucket.h b/src/memory_bucket.h new file mode 100644 index 0000000..180af80 --- /dev/null +++ b/src/memory_bucket.h @@ -0,0 +1,26 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#define kilobytes(num) num*1000 +#define megabytes(num) kilobytes(num*1000) + +typedef struct t_memory_bucket_entry +{ + char *data; + s32 length; + s32 cursor; +} memory_bucket_entry; + +typedef struct t_memory_bucket +{ + mutex bucket_mutex; + array buckets; +} memory_bucket; + +memory_bucket memory_bucket_init(s32 bucket_size); +void* memory_bucket_reserve(memory_bucket *bucket, s32 reserve_length); +void memory_bucket_reset(memory_bucket *bucket); +void memory_bucket_destroy(memory_bucket *bucket);
\ No newline at end of file diff --git a/src/mo_edit.c b/src/mo_edit.c new file mode 100644 index 0000000..019dbbe --- /dev/null +++ b/src/mo_edit.c @@ -0,0 +1,531 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#include "config.h" +#include "project_base.h" + +#include "languages.h" + +// TODO(Aldrik): option to disable menu item + +typedef struct t_translation +{ + s32 country_index; + char *value; +} translation; + +typedef struct t_term +{ + char *name; + translation translations[COUNTRY_CODE_COUNT]; +} term; + +typedef struct t_translation_project +{ + array languages; + array terms; + int selected_term_index; +} translation_project; + +translation_project *current_project = 0; + +scroll_state term_scroll; +scroll_state lang_scroll; +button_state btn_new_project; +button_state btn_new_language; +button_state btn_summary; +dropdown_state dd_available_countries; +textbox_state tb_filter; + +image *list_img; +image *exclaim_img; +image *delete_img; +image *logo_small_img; + +font *font_medium; +font *font_small; +font *font_mini; +s32 scroll_y = 0; + +#include "settings.h" +#include "settings.c" + +static void load_assets() +{ + list_img = assets_load_image(_binary____data_imgs_list_png_start, + _binary____data_imgs_list_png_end, false); + exclaim_img = assets_load_image(_binary____data_imgs_exclaim_png_start, + _binary____data_imgs_exclaim_png_end, false); + logo_small_img = assets_load_image(_binary____data_imgs_logo_64_png_start, + _binary____data_imgs_logo_64_png_end, true); + delete_img = assets_load_image(_binary____data_imgs_delete_png_start, + _binary____data_imgs_delete_png_end, false); + + font_medium = assets_load_font(_binary____data_fonts_mono_ttf_start, + _binary____data_fonts_mono_ttf_end, 18); + font_small = assets_load_font(_binary____data_fonts_mono_ttf_start, + _binary____data_fonts_mono_ttf_end, 15); + font_mini = assets_load_font(_binary____data_fonts_mono_ttf_start, + _binary____data_fonts_mono_ttf_end, 12); +} + +s32 get_available_country_index() +{ + s32 found_index = -1; + for (s32 x = 0; x < COUNTRY_CODE_COUNT; x++) + { + bool found = false; + for (s32 i = 0; i < current_project->languages.length; i++) + { + s32 ind = *(s32*)array_at(¤t_project->languages, i); + if (ind == x) found = true; + } + + if (!found) found_index = x; + } + + return found_index; +} + +bool country_has_been_added_to_project(s32 index) +{ + for (s32 i = 0; i < current_project->languages.length; i++) + { + s32 ind = *(s32*)array_at(¤t_project->languages, i); + + if (index == ind) return true; + } + + return false; +} + +s32 get_translated_count_for_language(s32 index) +{ + s32 count = 0; + for (s32 i = 0; i < current_project->terms.length; i++) + { + term *t = array_at(¤t_project->terms, i); + + for (s32 x = 0; x < COUNTRY_CODE_COUNT; x++) + { + translation *tr = &t->translations[x]; + if (tr->country_index == i) + { + count++; + } + } + } + + return count; +} + +void start_new_project() +{ + current_project = mem_alloc(sizeof(translation_project)); + + current_project->terms = array_create(sizeof(term)); + array_reserve(¤t_project->terms, 100); + current_project->terms.reserve_jump = 100; + + current_project->languages = array_create(sizeof(s32)); + array_reserve(¤t_project->languages, 100); + current_project->languages.reserve_jump = 100; + + for (s32 i = 0; i < 3; i++) + { + term t; + t.name = mem_alloc(10); + string_copyn(t.name, "meme_1", 10); + + for (s32 x = 0; x < COUNTRY_CODE_COUNT; x++) + { + translation tr; + tr.value = 0; + tr.country_index = -1; + t.translations[x] = tr; + } + + array_push(¤t_project->terms, &t); + } + + current_project->selected_term_index = -1; +} + +void load_config(settings_config *config) +{ + char *path = settings_config_get_string(config, "ACTIVE_PROJECT"); + char *locale_id = settings_config_get_string(config, "LOCALE"); + + if (locale_id) + set_locale(locale_id); + else + set_locale("en"); +} + +#if defined(OS_LINUX) || defined(OS_WIN) +int main(int argc, char **argv) +{ + platform_init(argc, argv); + + bool is_command_line_run = (argc > 1); + if (is_command_line_run) + { + handle_command_line_arguments(argc, argv); + return 0; + } + + char config_path_buffer[PATH_MAX]; + get_config_save_location(config_path_buffer); + + // load config + settings_config config = settings_config_load_from_file(config_path_buffer); + + s32 window_w = settings_config_get_number(&config, "WINDOW_WIDTH"); + s32 window_h = settings_config_get_number(&config, "WINDOW_HEIGHT"); + if (window_w <= 800 || window_h <= 600) + { + window_w = 800; + window_h = 600; + } + + platform_window window = platform_open_window("mo-edit", window_w, window_h, 0, 0, 800, 600); + main_window = &window; + + settings_page_create(); + + load_available_localizations(); + set_locale("en"); + + load_assets(); + + keyboard_input keyboard = keyboard_input_create(); + mouse_input mouse = mouse_input_create(); + + camera camera; + camera.x = 0; + camera.y = 0; + camera.rotation = 0; + + ui_create(&window, &keyboard, &mouse, &camera, font_small); + dd_available_countries = ui_create_dropdown(); + term_scroll = ui_create_scroll(1); + lang_scroll = ui_create_scroll(1); + btn_summary = ui_create_button(); + btn_new_project = ui_create_button(); + btn_new_language = ui_create_button(); + tb_filter = ui_create_textbox(MAX_INPUT_LENGTH); + + // asset worker + thread asset_queue_worker1 = thread_start(assets_queue_worker, NULL); + thread asset_queue_worker2 = thread_start(assets_queue_worker, NULL); + thread_detach(&asset_queue_worker1); + thread_detach(&asset_queue_worker2); + + load_config(&config); + + while(window.is_open) { + u64 last_stamp = platform_get_time(TIME_FULL, TIME_US); + platform_handle_events(&window, &mouse, &keyboard); + platform_set_cursor(&window, CURSOR_DEFAULT); + + settings_page_update_render(); + + platform_window_make_current(&window); + + static bool icon_loaded = false; + if (!icon_loaded && logo_small_img->loaded) + { + icon_loaded = true; + platform_set_icon(&window, logo_small_img); + } + + if (global_asset_collection.queue.queue.length == 0 && !global_asset_collection.done_loading_assets) + { + global_asset_collection.done_loading_assets = true; + } + + global_ui_context.layout.active_window = &window; + global_ui_context.keyboard = &keyboard; + global_ui_context.mouse = &mouse; + + render_clear(); + camera_apply_transformations(&window, &camera); + + global_ui_context.layout.width = global_ui_context.layout.active_window->width; + // begin ui + + ui_begin(1); + { + render_rectangle(0, 0, main_window->width, main_window->height, global_ui_context.style.background); + + ui_begin_menu_bar(); + { + if (ui_push_menu(localize("file"))) + { + // TODO(Aldrik): translate + if (ui_push_menu_item("Load Project", "Ctrl + O")) + { + } + // TODO(Aldrik): translate + if (ui_push_menu_item("Save Project", "Ctrl + S")) + { + } + // TODO(Aldrik): translate + if (ui_push_menu_item("Save Project As", "Ctrl + E")) + { + } + // TODO(Aldrik): translate + if (ui_push_menu_item("Export MO files", "Ctrl + X")) + { + } + ui_push_menu_item_separator(); + if (ui_push_menu_item(localize("quit"), "Ctrl + Q")) + { + window.is_open = false; + } + } + } + ui_end_menu_bar(); + + + // TODO(Aldrik): make this a setting, resizable panel + global_ui_context.layout.width = 300; + ui_push_vertical_dragbar(); + + if (current_project) + { + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_button_image(&btn_summary, "", list_img); + // TODO(Aldrik): translate + ui_push_textf_width(font_medium, "Terms", global_ui_context.layout.width-150); + + ui_push_button_image(&btn_summary, "", delete_img); + ui_push_button_image(&btn_summary, "", delete_img); + + } + ui_block_end(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + // TODO(Aldrik): translate + ui_push_textbox(&tb_filter, "Filter terms.."); + } + ui_block_end(); + + ui_push_separator(); + + term_scroll.height = main_window->height-global_ui_context.layout.offset_y; + ui_scroll_begin(&term_scroll); + { + for (s32 i = 0; i < current_project->terms.length; i++) + { + term *t = array_at(¤t_project->terms, i); + + ui_push_button_image(&btn_summary, "", delete_img); + //ui_push_image(exclaim_img, 14, 14, 1, rgb(255,255,255)); + ui_push_text_width(t->name, global_ui_context.layout.width-100); + + ui_block_end(); + } + } + ui_scroll_end(); + } + else + { + // TODO(Aldrik): translate + if (ui_push_button(&btn_new_project, "Create new project")) + { + start_new_project(); + } + } + + global_ui_context.layout.width = main_window->width - 310; + + global_ui_context.layout.offset_x = 310; + global_ui_context.layout.offset_y = MENU_BAR_HEIGHT + WIDGET_PADDING; + + if (current_project && current_project->selected_term_index >= 0) + { + + } + else if (current_project) + { + // overview + ui_block_begin(LAYOUT_HORIZONTAL); + { + // TODO(Aldrik): translate + ui_push_textf_width(font_medium, "Overview", 200); + + char info_text[60]; + sprintf(info_text, "%d terms, %d languages", current_project->terms.length, current_project->languages.length); + + color c = global_ui_context.style.foreground; + global_ui_context.style.foreground = rgb(110,110,110); + ui_push_textf(font_small, info_text); + global_ui_context.style.foreground = c; + } + ui_block_end(); + + ui_push_separator(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + s32 av_index = get_available_country_index(); + + if (dd_available_countries.selected_index == -1 && av_index >= 0) + dd_available_countries.selected_index = av_index; + + if (dd_available_countries.selected_index >= 0) + { + if (ui_push_dropdown(&dd_available_countries, + global_langues[dd_available_countries.selected_index].fullname)) + { + for (s32 i = 0; i < COUNTRY_CODE_COUNT; i++) + { + if (!country_has_been_added_to_project(i)) + { + if (ui_push_dropdown_item(0, global_langues[i].fullname, i)) + { + + } + } + } + } + + // TODO(Aldrik): translate + if (ui_push_button(&btn_new_language, "Add")) + { + array_push(¤t_project->languages,&dd_available_countries.selected_index); + + dd_available_countries.selected_index = -1; + } + } + } + ui_block_end(); + + if (dd_available_countries.selected_index >= 0) + ui_push_separator(); + + // languages + lang_scroll.height = main_window->height-global_ui_context.layout.offset_y; + ui_scroll_begin(&lang_scroll); + { + for (s32 i = 0; i < current_project->languages.length; i++) + { + button_state btn_remove = ui_create_button(); + + bool pressed = false; + if (ui_push_button_image(&btn_remove, "", delete_img)) + { + pressed = true; + } + + s32 index = *(s32*)array_at(¤t_project->languages, i); + ui_push_text_width(global_langues[index].fullname, global_ui_context.layout.width-200); + + color c = global_ui_context.style.foreground; + global_ui_context.style.foreground = rgb(110,110,110); + + char stats[50]; + sprintf(stats, "%d/%d translated", get_translated_count_for_language(index), current_project->terms.length); + ui_push_text(stats); + + global_ui_context.style.foreground = c; + + if (pressed) + { + array_remove_at(¤t_project->languages, i); + i--; + } + + ui_block_end(); + } + } + ui_scroll_end(); + } + else + { + // show no project loaded message/image + } + } + ui_end(); + // end ui + + assets_do_post_process(); + platform_window_swap_buffers(&window); + + u64 current_stamp = platform_get_time(TIME_FULL, TIME_US); + u64 diff = current_stamp - last_stamp; + float diff_ms = diff / 1000.0f; + last_stamp = current_stamp; + + if (diff_ms < TARGET_FRAMERATE) + { + double time_to_wait = (TARGET_FRAMERATE) - diff_ms; + thread_sleep(time_to_wait*1000); + } + } + + settings_page_hide_without_save(); + + // write config file +#if 0 + settings_config_set_string(&config, "SEARCH_DIRECTORY", textbox_path.buffer); + settings_config_set_number(&config, "SEARCH_DIRECTORIES", checkbox_recursive.state); + settings_config_set_string(&config, "SEARCH_TEXT", textbox_search_text.buffer); + settings_config_set_string(&config, "FILE_FILTER", textbox_file_filter.buffer); + settings_config_set_number(&config, "MAX_THEAD_COUNT", global_settings_page.max_thread_count); + settings_config_set_number(&config, "MAX_FILE_SIZE", global_settings_page.max_file_size); + + vec2 win_size = platform_get_window_size(&window); + settings_config_set_number(&config, "WINDOW_WIDTH", win_size.x); + settings_config_set_number(&config, "WINDOW_HEIGHT", win_size.y); + + settings_config_set_number(&config, "STYLE", global_ui_context.style.id); + settings_config_set_number(&config, "DOUBLE_CLICK_ACTION", global_settings_page.selected_double_click_selection_option); + + if (global_localization.active_localization != 0) + { + char *current_locale_id = locale_get_id(); + if (current_locale_id) + { + settings_config_set_string(&config, "LOCALE", current_locale_id); + } + } + + settings_config_write_to_file(&config, config_path_buffer); + settings_config_destroy(&config); +#endif + + settings_page_destroy(); + + destroy_available_localizations(); + +#if 0 + // cleanup ui + ui_destroy_textbox(&textbox_path); + ui_destroy_textbox(&textbox_search_text); + ui_destroy_textbox(&textbox_file_filter); +#endif + ui_destroy(); + + // delete assets + assets_destroy_image(list_img); + assets_destroy_image(logo_small_img); + assets_destroy_image(delete_img); + + assets_destroy_font(font_small); + assets_destroy_font(font_mini); + + keyboard_input_destroy(&keyboard); + platform_destroy_window(&window); + + platform_destroy(); + + return 0; +} +#endif
\ No newline at end of file diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000..d256c5f --- /dev/null +++ b/src/platform.h @@ -0,0 +1,221 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_PLATFORM +#define INCLUDE_PLATFORM + +typedef struct t_platform_window platform_window; + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +typedef struct t_found_file +{ + char *matched_filter; + char *path; +} found_file; + +typedef struct t_file_match +{ + found_file file; + s16 file_error; + s32 file_size; + + u32 line_nr; + s32 word_match_offset; + s32 word_match_length; + s32 word_match_offset_x; // highlight render offset + s32 word_match_width; // highlight render width + char *line_info; // will be null when no match is found +} file_match; + +typedef struct t_search_result +{ + array work_queue; + array files; + array matches; + u64 find_duration_us; + array errors; + bool show_error_message; // error occured + bool found_file_matches; // found/finding file matches + s32 files_searched; + s32 files_matched; + s32 search_result_source_dir_len; + bool match_found; // found text match + mutex mutex; + bool walking_file_system; + bool cancel_search; + bool done_finding_matches; + s32 search_id; + u64 start_time; + bool done_finding_files; + memory_bucket mem_bucket; + bool is_command_line_search; + bool threads_closed; + + char *export_path; + char *file_filter; + char *directory_to_search; + char *text_to_find; + s32 max_thread_count; + s32 max_file_size; + bool is_recursive; +} search_result; + +typedef struct t_find_text_args +{ + file_match file; + search_result *search_result_buffer; +} find_text_args; + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +typedef struct t_file_content +{ + s64 content_length; + void *content; + s16 file_error; +} file_content; + +typedef enum t_time_type +{ + TIME_FULL, // realtime + TIME_THREAD, // run time for calling thread + TIME_PROCESS, // run time for calling process +} time_type; + +typedef enum t_time_precision +{ + TIME_NS, // nanoseconds + TIME_US, // microseconds + TIME_MILI_S, // miliseconds + TIME_S, // seconds +} time_precision; + +typedef struct t_cpu_info +{ + s32 model; + char model_name[255]; + float32 frequency; + u32 cache_size; + u32 cache_alignment; +} cpu_info; + +typedef enum t_file_dialog_type +{ + OPEN_FILE, + OPEN_DIRECTORY, + SAVE_FILE, +} file_dialog_type; + +typedef enum t_file_open_error +{ + FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS = 1, + FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM = 2, + FILE_ERROR_NO_ACCESS = 3, + FILE_ERROR_NOT_FOUND = 4, + FILE_ERROR_CONNECTION_ABORTED = 5, + FILE_ERROR_CONNECTION_REFUSED = 6, + FILE_ERROR_NETWORK_DOWN = 7, + FILE_ERROR_REMOTE_IO_ERROR = 8, + FILE_ERROR_STALE = 9, // NFS server file is removed/renamed + FILE_ERROR_GENERIC = 10, +} file_open_error; + +struct open_dialog_args +{ + char *buffer; + char *file_filter; + char *start_path; + char *default_save_file_extension; + file_dialog_type type; +}; + +typedef struct t_list_file_args +{ + array *list; + char *start_dir; + char *pattern; + bool recursive; + bool include_directories; + bool *state; + bool *is_cancelled; + memory_bucket *bucket; +} list_file_args; + +typedef enum t_cursor_type +{ + CURSOR_DEFAULT, + CURSOR_POINTER, +} cursor_type; + +typedef struct t_vec2 +{ + s32 x; + s32 y; +} vec2; + +platform_window *main_window = 0; +platform_window *settings_window = 0; + +bool platform_window_is_valid(platform_window *window); +platform_window platform_open_window(char *name, u16 width, u16 height, u16 max_w, u16 max_h, u16 min_w, u16 min_h); +void platform_get_focus(platform_window *window); +bool platform_set_clipboard(platform_window *window, char *buffer); +bool platform_get_clipboard(platform_window *window, char *buffer); +void platform_window_set_size(platform_window *window, u16 width, u16 height); +void platform_window_set_position(platform_window *window, u16 x, u16 y); +void platform_destroy_window(platform_window *window); +void platform_handle_events(platform_window *window, mouse_input *mouse, keyboard_input *keyboard); +void platform_window_swap_buffers(platform_window *window); +void platform_set_cursor(platform_window *window, cursor_type type); +void platform_window_set_title(platform_window *window, char *name); +file_content platform_read_file_content(char *path, const char *mode); +bool platform_write_file_content(char *path, const char *mode, char *buffer, s32 len); +void platform_destroy_file_content(file_content *content); +bool get_active_directory(char *buffer); +bool set_active_directory(char *path); +void platform_show_message(platform_window *window, char *message, char *title); +array get_filters(char *filter); +void platform_list_files_block(array *list, char *start_dir, array filters, bool recursive, memory_bucket *bucket, bool include_directories, bool *is_cancelled); +void platform_list_files(array *list, char *start_dir, char *filter, bool recursive, memory_bucket *bucket, bool *is_cancelled, bool *state); +void platform_open_file_dialog(file_dialog_type type, char *buffer, char *file_filter, char *start_path); +bool is_platform_in_darkmode(); +void *platform_open_file_dialog_block(void *arg); +char *platform_get_full_path(char *file); +void platform_open_url(char *command); +void platform_run_command(char *command); +void platform_window_make_current(platform_window *window); +void platform_init(int argc, char **argv); +void platform_destroy(); +void platform_set_icon(platform_window *window, image *img); +void platform_autocomplete_path(char *buffer, bool want_dir); +bool platform_directory_exists(char *path); +bool platform_file_exists(char *path); +void platform_show_alert(char *title, char *message); +char *get_config_save_location(char *buffer); +char *get_file_extension(char *path); +void get_name_from_path(char *buffer, char *path); +void get_directory_from_path(char *buffer, char *path); +vec2 platform_get_window_size(platform_window *window); +s32 filter_matches(array *filters, char *string, char **matched_filter); + +u64 platform_get_time(time_type time_type, time_precision precision); +s32 platform_get_memory_size(); +s32 platform_get_cpu_count(); + +u64 string_to_u64(char *str); +u32 string_to_u32(char *str); +u16 string_to_u16(char *str); +u8 string_to_u8(char *str); + +s64 string_to_s64(char *str); +s32 string_to_s32(char *str); +s16 string_to_s16(char *str); +s8 string_to_s8(char *str); + +#endif
\ No newline at end of file diff --git a/src/platform_shared.c b/src/platform_shared.c new file mode 100644 index 0000000..4107ca3 --- /dev/null +++ b/src/platform_shared.c @@ -0,0 +1,244 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +void get_name_from_path(char *buffer, char *path) +{ + buffer[0] = 0; + + s32 len = strlen(path); + if (len == 1) + { + return; + } + + char *path_end = path + len; +#ifdef OS_LINUX + while (*path_end != '/' && path_end >= path) + { + --path_end; + } +#endif +#ifdef OS_WIN + while (*path_end != '\\' && path_end >= path) + { + --path_end; + } +#endif + + string_copyn(buffer, path_end+1, MAX_INPUT_LENGTH); +} + +void get_directory_from_path(char *buffer, char *path) +{ + buffer[0] = 0; + + s32 len = strlen(path); + if (len == 1) + { + return; + } + + char *path_end = path + len; +#ifdef OS_LINUX + while (*path_end != '/' && path_end >= path) + { + --path_end; + } +#endif +#ifdef OS_WIN + while (*path_end != '\\' && path_end >= path) + { + --path_end; + } +#endif + + s32 offset = path_end - path; + char ch = path[offset+1]; + path[offset+1] = 0; + string_copyn(buffer, path, MAX_INPUT_LENGTH); + path[offset+1] = ch; +} + +void platform_autocomplete_path(char *buffer, bool want_dir) +{ + char dir[MAX_INPUT_LENGTH]; + char name[MAX_INPUT_LENGTH]; + get_directory_from_path(dir, buffer); + get_name_from_path(name, buffer); + + // nothing to autocomplete + if (name[0] == 0) + { + return; + } + + // create filter + string_appendn(name, "*", MAX_INPUT_LENGTH); + + array files = array_create(sizeof(found_file)); + array filters = get_filters(name); + bool is_cancelled = false; + platform_list_files_block(&files, dir, filters, false, 0, want_dir, &is_cancelled); + + s32 index_to_take = -1; + if (want_dir) + { + for (s32 i = 0; i < files.length; i++) + { + found_file *file = array_at(&files, i); + + if (platform_directory_exists(file->path)) + { + index_to_take = i; + break; + } + } + } + else + { + index_to_take = 0; + } + + array_destroy(&filters); + + if (files.length > 0 && index_to_take != -1) + { + found_file *file = array_at(&files, index_to_take); + string_copyn(buffer, file->path, MAX_INPUT_LENGTH); + } + + for (s32 i = 0; i < files.length; i++) + { + found_file *match = array_at(&files, i); + mem_free(match->matched_filter); + mem_free(match->path); + } + array_destroy(&files); +} + +array get_filters(char *pattern) +{ + array result = array_create(MAX_INPUT_LENGTH); + + char current_filter[MAX_INPUT_LENGTH]; + s32 filter_len = 0; + while(*pattern) + { + char ch = *pattern; + + if (ch == ',') + { + current_filter[filter_len] = 0; + array_push(&result, current_filter); + filter_len = 0; + } + else + { + if(filter_len < MAX_INPUT_LENGTH-1) + { + current_filter[filter_len++] = ch; + } + else + { + current_filter[filter_len] = ch; + } + } + + pattern++; + } + current_filter[filter_len] = 0; + array_push(&result, current_filter); + + return result; +} + +void *platform_list_files_thread(void *args) +{ + list_file_args *info = args; + + array filters = get_filters(info->pattern); + + array *list = info->list; + char *start_dir = info->start_dir; + bool recursive = info->recursive; + + platform_list_files_block(info->list, info->start_dir, filters, info->recursive, info->bucket, info->include_directories, info->is_cancelled); + + mutex_lock(&info->list->mutex); + //if (!(*info->is_cancelled)) + *(info->state) = true; + mutex_unlock(&info->list->mutex); + + array_destroy(&filters); + + return 0; +} + +void platform_list_files(array *list, char *start_dir, char *filter, bool recursive, memory_bucket *bucket, bool *is_cancelled, bool *state) +{ + list_file_args *args = memory_bucket_reserve(bucket, sizeof(list_file_args)); + args->list = list; + args->start_dir = start_dir; + args->pattern = filter; + args->recursive = recursive; + args->state = state; + args->include_directories = 0; + args->bucket = bucket; + args->is_cancelled = is_cancelled; + + thread thr = thread_start(platform_list_files_thread, args); + thread_detach(&thr); +} + +void platform_open_file_dialog(file_dialog_type type, char *buffer, char *file_filter, char *start_path) +{ + struct open_dialog_args *args = mem_alloc(sizeof(struct open_dialog_args)); + args->buffer = buffer; + args->type = type; + args->file_filter = file_filter; + args->start_path = start_path; + + thread thr; + thr.valid = false; + + while (!thr.valid) + thr = thread_start(platform_open_file_dialog_block, args); + thread_detach(&thr); +} + +void destroy_found_file_array(array *found_files) +{ + for (s32 i = 0; i < found_files->length; i++) + { + found_file *f = array_at(found_files, i); + mem_free(f->matched_filter); + mem_free(f->path); + } + array_destroy(found_files); +} + +char *get_file_extension(char *path) +{ + while(*path != '.' && *path) + { + path++; + } + return path; +} + +s32 filter_matches(array *filters, char *string, char **matched_filter) +{ + for (s32 i = 0; i < filters->length; i++) + { + char *filter = array_at(filters, i); + if (string_match(filter, string)) + { + *matched_filter = filter; + return strlen(filter); + } + } + return -1; +} diff --git a/src/project_base.h b/src/project_base.h new file mode 100644 index 0000000..0563934 --- /dev/null +++ b/src/project_base.h @@ -0,0 +1,111 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_PROJECT_BASE +#define INCLUDE_PROJECT_BASE + +#ifdef _WIN32 +#define OS_WIN +#include <windows.h> +#include <time.h> +#endif +#ifdef __linux__ +#define OS_LINUX +#include <sys/times.h> +#include <sys/vtimes.h> +#endif +#ifdef __APPLE__ +#define OS_OSX +#error platform not supported +#endif + +#include "stdint.h" +#include "string.h" +#include "assert.h" + +#include <GL/gl.h> +#ifdef OS_LINUX +#include <GL/glx.h> +#endif +#include <GL/glu.h> +#include <GL/glext.h> + +#define s8 int8_t +#define s16 int16_t +#define s32 int32_t +#define s64 int64_t + +#define u8 uint8_t +#define u16 uint16_t +#define u32 uint32_t +#define u64 uint64_t + +#define float32 float +#define float64 double + +#ifdef OS_LINUX +#define bool uint8_t +#endif +#ifdef OS_WIN +#define bool _Bool +#endif + +#define true 1 +#define false 0 + +#include "thread.h" +#include "array.h" +#include "memory.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "external/stb_image.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#include "external/stb_truetype.h" + +#include "external/utf8.h" +#include "input.h" +#include "assets.h" +#include "memory_bucket.h" +#include "platform.h" +#include "render.h" +#include "camera.h" +#include "ui.h" +#include "string_utils.h" +#include "settings_config.h" +#include "localization.h" +#include "command_line.h" + +#include "platform_shared.c" + +#ifdef OS_LINUX +#define DEFAULT_DIRECTORY "/home/" +#include "linux/thread.c" +#include "linux/platform.c" +#endif + +#ifdef OS_WIN +#define DEFAULT_DIRECTORY "C:/" +#include "windows/thread.c" +#include "windows/platform.c" +#endif + +#include "input.c" +#include "array.c" +#include "assets.c" +#include "render.c" +#include "camera.c" +#include "ui.c" +#include "string_utils.c" +#include "settings_config.c" +#include "localization.c" +#include "memory_bucket.c" +#include "command_line.c" + +#include "external/cJSON.h" +#include "external/cJSON.c" + +#endif
\ No newline at end of file diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..e126133 --- /dev/null +++ b/src/render.c @@ -0,0 +1,446 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +inline void render_clear() +{ + glClearColor(255/255.0, 255/255.0, 255/255.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +inline void render_set_rotation(float32 rotation, float32 x, float32 y, s32 depth) +{ + glRotatef(rotation, x, y, depth); +} + +inline void set_render_depth(s32 depth) +{ + render_depth = depth; +} + +void render_image(image *image, s32 x, s32 y, s32 width, s32 height) +{ + assert(image); + if (image->loaded) + { + glBindTexture(GL_TEXTURE_2D, image->textureID); + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glColor4f(1., 1., 1., 1.); + glTexCoord2i(0, 0); glVertex3i(x, y, render_depth); + glTexCoord2i(0, 1); glVertex3i(x, y+height, render_depth); + glTexCoord2i(1, 1); glVertex3i(x+width, y+height, render_depth); + glTexCoord2i(1, 0); glVertex3i(x+width, y, render_depth); + glEnd(); + + glDisable(GL_TEXTURE_2D); + } +} + +void render_image_tint(image *image, s32 x, s32 y, s32 width, s32 height, color tint) +{ + assert(image); + if (image->loaded) + { + glBindTexture(GL_TEXTURE_2D, image->textureID); + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + glTexCoord2i(0, 0); glVertex3i(x, y, render_depth); + glTexCoord2i(0, 1); glVertex3i(x, y+height, render_depth); + glTexCoord2i(1, 1); glVertex3i(x+width, y+height, render_depth); + glTexCoord2i(1, 0); glVertex3i(x+width, y, render_depth); + glEnd(); + + glDisable(GL_TEXTURE_2D); + } +} + +s32 render_text_ellipsed(font *font, s32 x, s32 y, s32 maxw, char *text, color tint) +{ + if (!font->loaded) + return 0; + + glEnable(GL_TEXTURE_2D); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + + char *ellipse = "..."; + bool in_ellipse = false; + + s32 x_ = x; + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + glyph g = font->glyphs[ch]; + + glBindTexture(GL_TEXTURE_2D, g.textureID); + glBegin(GL_QUADS); + + s32 width = g.width; + + s32 y_ = y + font->px_h + g.yoff; + + glTexCoord2i(0, 0); glVertex3i(x_,y_, render_depth); + glTexCoord2i(0, 1); glVertex3i(x_,y_+g.height, render_depth); + glTexCoord2i(1, 1); glVertex3i(x_+g.width,y_+g.height, render_depth); + glTexCoord2i(1, 0); glVertex3i(x_+g.width,y_, render_depth); + + glEnd(); + + /* add kerning */ + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x_ += kern * font->scale; + x_ += g.width+g.xoff; + + if (!in_ellipse && (x_-x) > maxw-(font->glyphs['.'].width*3)) + { + in_ellipse = true; + text = ellipse; + } + } + + glDisable(GL_TEXTURE_2D); + + return maxw; +} + +s32 render_text(font *font, s32 x, s32 y, char *text, color tint) +{ + if (!font->loaded) + return 0; + + glEnable(GL_TEXTURE_2D); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + + s32 x_ = x; + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + glyph g = font->glyphs[ch]; + + glBindTexture(GL_TEXTURE_2D, g.textureID); + glBegin(GL_QUADS); + + s32 width = g.width; + + s32 y_ = y + font->px_h + g.yoff; + + glTexCoord2i(0, 0); glVertex3i(x_,y_, render_depth); + glTexCoord2i(0, 1); glVertex3i(x_,y_+g.height, render_depth); + glTexCoord2i(1, 1); glVertex3i(x_+g.width,y_+g.height, render_depth); + glTexCoord2i(1, 0); glVertex3i(x_+g.width,y_, render_depth); + + glEnd(); + + /* add kerning */ + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x_ += kern * font->scale; + x_ += g.width+g.xoff; + } + + glDisable(GL_TEXTURE_2D); + + return x_ - x; +} + +s32 render_text_cutoff(font *font, s32 x, s32 y, char *text, color tint, u16 cutoff_width) +{ + if (!font->loaded) + return 0; + + glEnable(GL_TEXTURE_2D); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + + s32 x_ = x; + s32 y_ = y; + bool is_new_line = false; + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + if (ch == '\n') + { + x_ = x; + y_ += font->size; + is_new_line = true; + continue; + } + + if (is_new_line && ch == ' ') + { + is_new_line = false; + continue; + } + else if (is_new_line && ch != ' ') + { + is_new_line = false; + } + + + glyph g = font->glyphs[ch]; + + glBindTexture(GL_TEXTURE_2D, g.textureID); + glBegin(GL_QUADS); + + s32 width = g.width; + + s32 y__ = y_ + font->px_h + g.yoff; + + glTexCoord2i(0, 0); glVertex3i(x_,y__, render_depth); + glTexCoord2i(0, 1); glVertex3i(x_,y__+g.height, render_depth); + glTexCoord2i(1, 1); glVertex3i(x_+g.width,y__+g.height, render_depth); + glTexCoord2i(1, 0); glVertex3i(x_+g.width,y__, render_depth); + + glEnd(); + + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x_ += kern * font->scale; + + x_ += g.width+g.xoff; + if (x_ > x+cutoff_width) + { + x_ = x; + y_ += font->size; + is_new_line = true; + } + } + + glDisable(GL_TEXTURE_2D); + + return (y_ - y) + font->size; + +} + +s32 calculate_cursor_position(font *font, char *text, s32 click_x) +{ + if (!font->loaded) + return 0; + + s32 x = 0; + s32 index = 0; + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + + glyph g = font->glyphs[ch]; + + s32 width = g.width; + s32 width_next = font->glyphs[ch_next].width; + + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x += kern * font->scale; + + x += g.width+g.xoff; + + if (x - (width_next/5) > click_x) + { + return index; + } + + ++index; + } + + return index; +} + +s32 calculate_text_width_from_upto(font *font, char *text, s32 from, s32 index) +{ + if (!font->loaded) + return 0; + + s32 x = 0; + utf8_int32_t ch; + s32 i = 0; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (index == i) return x; + + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + glyph g = font->glyphs[ch]; + s32 width = g.width; + + if (i >= from) + { + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x += kern * font->scale; + + x += g.width+g.xoff; + } + + i++; + } + + return x; +} + +s32 calculate_text_width_upto(font *font, char *text, s32 index) +{ + if (!font->loaded) + return 0; + + s32 x = 0; + utf8_int32_t ch; + s32 i = 0; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (index == i) return x; + + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + glyph g = font->glyphs[ch]; + s32 width = g.width; + + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x += kern * font->scale; + + x += g.width+g.xoff; + + i++; + } + + return x; +} + +s32 calculate_text_width(font *font, char *text) +{ + if (!font->loaded) + return 0; + + s32 x = 0; + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch == 9) ch = 32; + utf8_int32_t ch_next; + utf8codepoint(text, &ch_next); + if (ch < TEXT_CHARSET_START || ch > TEXT_CHARSET_END) + { + ch = 0x3f; + } + + glyph g = font->glyphs[ch]; + s32 width = g.width; + + int kern = stbtt_GetCodepointKernAdvance(&font->info, ch, ch_next); + if (kern != 0) x += kern * font->scale; + + x += g.width+g.xoff; + } + + return x; +} + +void render_triangle(s32 x, s32 y, s32 w, s32 h, color tint) +{ + glBegin(GL_TRIANGLES); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + glVertex3i(x+(w/2), y+h, render_depth); + glVertex3i(x, y, render_depth); + glVertex3i(x+w, y, render_depth); + glEnd(); +} + +void render_rectangle(s32 x, s32 y, s32 width, s32 height, color tint) +{ + glBegin(GL_QUADS); + glColor4f(tint.r/255.0f, tint.g/255.0f, tint.b/255.0f, tint.a/255.0f); + glVertex3i(x, y, render_depth); + glVertex3i(x, y+height, render_depth); + glVertex3i(x+width, y+height, render_depth); + glVertex3i(x+width, y, render_depth); + glEnd(); +} + +void render_rectangle_tint(s32 x, s32 y, s32 width, s32 height, color tint[4]) +{ + glBegin(GL_QUADS); + glColor4f(tint[0].r/255.0f, tint[0].g/255.0f, tint[0].b/255.0f, tint[0].a/255.0f); + glVertex3i(x, y, render_depth); + glColor4f(tint[1].r/255.0f, tint[1].g/255.0f, tint[1].b/255.0f, tint[1].a/255.0f); + glVertex3i(x, y+height, render_depth); + glColor4f(tint[2].r/255.0f, tint[2].g/255.0f, tint[2].b/255.0f, tint[2].a/255.0f); + glVertex3i(x+width, y+height, render_depth); + glColor4f(tint[3].r/255.0f, tint[3].g/255.0f, tint[3].b/255.0f, tint[3].a/255.0f); + glVertex3i(x+width, y, render_depth); + glEnd(); +} + +void render_rectangle_outline(s32 x, s32 y, s32 width, s32 height, u16 outline_w, color tint) +{ + // left + render_rectangle(x, y, outline_w, height, tint); + // right + render_rectangle(x+width-outline_w, y, outline_w, height, tint); + // top + render_rectangle(x+outline_w, y, width-(outline_w*2), outline_w, tint); + // bottom + render_rectangle(x+outline_w, y+height-outline_w, width-(outline_w*2), outline_w, tint); +} + +void render_set_scissor(platform_window *window, s32 x, s32 y, s32 w, s32 h) +{ + glEnable(GL_SCISSOR_TEST); + glScissor(x-1, window->height-h-y-1, w+1, h+1); +} + +vec4 render_get_scissor() +{ + vec4 vec; + glGetIntegerv(GL_SCISSOR_BOX, (GLint*)(&vec)); + vec.x += 1; + vec.y += 1; + vec.w -= 1; + vec.h -= 1; + return vec; +} + +void render_reset_scissor() +{ + glDisable(GL_SCISSOR_TEST); +}
\ No newline at end of file diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..4d29eea --- /dev/null +++ b/src/render.h @@ -0,0 +1,61 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_RENDER +#define INCLUDE_RENDER + +typedef struct t_color { + u8 r; + u8 g; + u8 b; + u8 a; +} color; + +typedef struct t_vec4 +{ + s32 x; + s32 y; + s32 w; + s32 h; +} vec4; + +s32 render_depth = 1; +void set_render_depth(s32 depth); + +#define rgb(r_,g_,b_) (color){ r_, g_, b_, 255 } +#define rgba(r_,g_,b_,a_) (color){r_,g_,b_,a_} + +void render_clear(); + +// images +void render_image(image *image, s32 x, s32 y, s32 width, s32 height); +void render_image_tint(image *image, s32 x, s32 y, s32 width, s32 height, color tint); + +// text +s32 render_text(font *font, s32 x, s32 y, char *text, color tint); +s32 render_text_ellipsed(font *font, s32 x, s32 y, s32 maxw, char *text, color tint); +s32 render_text_cutoff(font *font, s32 x, s32 y, char *text, color tint, u16 cutoff_width); +s32 render_text_vertical(font *font, s32 x, s32 y, char *text, color tint); + +s32 calculate_cursor_position(font *font, char *text, s32 click_x); +s32 calculate_text_width(font *font, char *text); +s32 calculate_text_width_upto(font *font, char *text, s32 index); +s32 calculate_text_width_from_upto(font *font, char *text, s32 from, s32 index); + +// primitives +void render_rectangle(s32 x, s32 y, s32 width, s32 height, color tint); +void render_rectangle_tint(s32 x, s32 y, s32 width, s32 height, color tint[4]); +void render_rectangle_outline(s32 x, s32 y, s32 width, s32 height, u16 outline_w, color tint); +void render_triangle(s32 x, s32 y, s32 w, s32 h, color tint); + +// utils +void render_set_scissor(platform_window *window, s32 x, s32 y, s32 w, s32 h); +vec4 render_get_scissor(); +void render_reset_scissor(); + +void render_set_rotation(float32 rotation, float32 x, float32 y, s32 depth); + +#endif
\ No newline at end of file diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..82ca623 --- /dev/null +++ b/src/save.c @@ -0,0 +1,380 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +search_result *create_empty_search_result(); +void* destroy_search_result_thread(void *arg); + +static void write_json_file(char *buffer, s32 length, search_result *search_result) +{ + array matches = search_result->matches; + + cJSON *result = cJSON_CreateObject(); + if (cJSON_AddStringToObject(result, "search_directory", + search_result->directory_to_search) == NULL) + return; + + if (cJSON_AddStringToObject(result, "filter", + search_result->file_filter) == NULL) + return; + + if (cJSON_AddStringToObject(result, "search_query", + search_result->text_to_find) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "duration_us", + search_result->find_duration_us) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "show_error", + search_result->show_error_message) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "file_match_found", + search_result->found_file_matches) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "files_searched", + search_result->files_searched) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "files_matched", + search_result->files_matched) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "query_match_found", + search_result->match_found) == NULL) + return; + + if (cJSON_AddNumberToObject(result, "recursive_search", + search_result->is_recursive) == NULL) + return; + + cJSON *match_list = cJSON_AddArrayToObject(result, "match_list"); + + if (!match_list) return; + + for (s32 i = 0; i < matches.length; i++) + { + file_match* m = array_at(&matches, i); + + cJSON *item = cJSON_CreateObject(); + + if (cJSON_AddStringToObject(item, "path", + m->file.path) == NULL) + return; + + if (cJSON_AddStringToObject(item, "matched_filter", + m->file.matched_filter) == NULL) + return; + + if (cJSON_AddNumberToObject(item, "word_offset", + m->word_match_offset) == NULL) + return; + + if (cJSON_AddNumberToObject(item, "word_length", + m->word_match_length) == NULL) + return; + + if (cJSON_AddNumberToObject(item, "file_error", + m->file_error) == NULL) + return; + + if (cJSON_AddNumberToObject(item, "line_nr", + m->line_nr) == NULL) + return; + + if (cJSON_AddNumberToObject(item, "file_size", + m->file_size) == NULL) + return; + + if (m->line_info) + { + if (cJSON_AddStringToObject(item, "line_info", + m->line_info) == NULL) + return; + } + else + { + if (cJSON_AddNumberToObject(item, "line_info", 0) == NULL) + return; + } + + cJSON_AddItemToArray(match_list, item); + } + + cJSON_PrintPreallocated(result, buffer, length, true); + cJSON_Delete(result); +} + +static void *export_result_d(void *arg) +{ + search_result *search_result = arg; + + array matches = search_result->files; + + char path_buf[MAX_INPUT_LENGTH]; + path_buf[0] = 0; + + char start_path[MAX_INPUT_LENGTH]; + snprintf(start_path, MAX_INPUT_LENGTH, "%s%s", binary_path, ""); + + char default_save_file_extension[50]; + string_copyn(default_save_file_extension, "json", 50); + + if (!search_result->is_command_line_search) + { + struct open_dialog_args *args = mem_alloc(sizeof(struct open_dialog_args)); + args->buffer = path_buf; + args->type = SAVE_FILE; + args->file_filter = SEARCH_RESULT_FILE_EXTENSION; + args->start_path = start_path; + args->default_save_file_extension = default_save_file_extension; + + platform_open_file_dialog_block(args); + } + else + { + string_copyn(path_buf, search_result->export_path, MAX_INPUT_LENGTH); + } + + char tmp_dir_buffer[MAX_INPUT_LENGTH]; + get_directory_from_path(tmp_dir_buffer, path_buf); + + char tmp_name_buffer[MAX_INPUT_LENGTH]; + get_name_from_path(tmp_name_buffer, path_buf); + + if (string_equals(path_buf, "")) return 0; + if (string_equals(tmp_name_buffer, "")) return 0; + if (!platform_directory_exists(tmp_dir_buffer)) return 0; + + s32 size = matches.length * (MAX_INPUT_LENGTH*10); + char *buffer = mem_alloc(size); + memset(buffer, 0, size); + + char *file_extension = get_file_extension(path_buf); + if (string_equals(file_extension, ".json") || string_equals(file_extension, "")) + { + write_json_file(buffer, size, search_result); + } + + if (string_equals(file_extension, "")) + { + string_appendn(path_buf, ".json", MAX_INPUT_LENGTH); + } + + platform_write_file_content(path_buf, "w", buffer, size); + + return 0; +} + +bool export_results(search_result *search_result) +{ + thread thr; + thr.valid = false; + + while (!thr.valid) + thr = thread_start(export_result_d, search_result); + + if (!search_result->is_command_line_search) + thread_detach(&thr); + else + thread_join(&thr); + + return true; +} + +static bool read_json_file(char *buffer, s32 size, search_result *search_result) +{ + cJSON *result = cJSON_Parse(buffer); + if (!result) return false; + + cJSON *search_directory = cJSON_GetObjectItemCaseSensitive(result, "search_directory"); + string_copyn(textbox_path.buffer, search_directory->valuestring, MAX_INPUT_LENGTH); + string_copyn(search_result->directory_to_search, search_directory->valuestring, MAX_INPUT_LENGTH); + + cJSON *filter = cJSON_GetObjectItemCaseSensitive(result, "filter"); + string_copyn(textbox_file_filter.buffer, filter->valuestring, MAX_INPUT_LENGTH); + string_copyn(search_result->file_filter, filter->valuestring, MAX_INPUT_LENGTH); + + cJSON *search_query = cJSON_GetObjectItemCaseSensitive(result, "search_query"); + string_copyn(textbox_search_text.buffer, search_query->valuestring, MAX_INPUT_LENGTH); + string_copyn(search_result->text_to_find, search_query->valuestring, MAX_INPUT_LENGTH); + + cJSON *duration_us = cJSON_GetObjectItemCaseSensitive(result, "duration_us"); + search_result->find_duration_us = duration_us->valueint; + + cJSON *show_error = cJSON_GetObjectItemCaseSensitive(result, "show_error"); + search_result->show_error_message = show_error->valueint; + + cJSON *file_match_found = cJSON_GetObjectItemCaseSensitive(result, "file_match_found"); + search_result->found_file_matches = file_match_found->valueint; + + cJSON *files_searched = cJSON_GetObjectItemCaseSensitive(result, "files_searched"); + search_result->files_searched = files_searched->valueint; + + cJSON *files_matched = cJSON_GetObjectItemCaseSensitive(result, "files_matched"); + search_result->files_matched = files_matched->valueint; + + cJSON *query_match_found = cJSON_GetObjectItemCaseSensitive(result, "query_match_found"); + search_result->match_found = query_match_found->valueint; + + cJSON *recursive = cJSON_GetObjectItemCaseSensitive(result, "recursive_search"); + search_result->is_recursive = recursive->valueint; + checkbox_recursive.state = search_result->is_recursive; + + search_result->search_result_source_dir_len = strlen(search_result->directory_to_search); + + cJSON *file_list = cJSON_GetObjectItem(result, "match_list"); + cJSON *file; + cJSON_ArrayForEach(file, file_list) + { + file_match new_match; + + //// + cJSON *path = cJSON_GetObjectItem(file, "path"); + new_match.file.path = memory_bucket_reserve(&search_result->mem_bucket, strlen(path->valuestring)+1); + string_copyn(new_match.file.path, path->valuestring, strlen(path->valuestring)+1); + + //// + cJSON *matched_filter = cJSON_GetObjectItem(file, "matched_filter"); + new_match.file.matched_filter = memory_bucket_reserve(&search_result->mem_bucket, strlen(matched_filter->valuestring)+1); + string_copyn(new_match.file.matched_filter, matched_filter->valuestring, strlen(matched_filter->valuestring)+1); + + //// + cJSON *word_offset = cJSON_GetObjectItem(file, "word_offset"); + new_match.word_match_offset = word_offset->valueint; + + //// + cJSON *word_length = cJSON_GetObjectItem(file, "word_length"); + new_match.word_match_length = word_length->valueint; + + //// + cJSON *file_error = cJSON_GetObjectItem(file, "file_error"); + new_match.file_error = file_error->valueint; + + //// + cJSON *line_nr = cJSON_GetObjectItem(file, "line_nr"); + new_match.line_nr = line_nr->valueint; + + //// + cJSON *file_size = cJSON_GetObjectItem(file, "file_size"); + new_match.file_size = file_size->valueint; + + //// + cJSON *line_info = cJSON_GetObjectItem(file, "line_info"); + if (cJSON_IsString(line_info)) + { + new_match.line_info = memory_bucket_reserve(&search_result->mem_bucket, strlen(line_info->valuestring)+1); + string_copyn(new_match.line_info, line_info->valuestring, strlen(line_info->valuestring)+1); + search_result->match_found = true; + } + else + { + new_match.line_info = 0; + } + + // calculate highlight offsets + if (new_match.line_info) + { + new_match.word_match_offset_x = + calculate_text_width_upto(font_mini, new_match.line_info, new_match.word_match_offset); + + new_match.word_match_width = + calculate_text_width_from_upto(font_mini, new_match.line_info, new_match.word_match_offset, new_match.word_match_offset + new_match.word_match_length); + } + + array_push(&search_result->matches, &new_match); + } + + return true; +} + +void import_results_from_file(char *path_buf) +{ + char *file_extension = get_file_extension(path_buf); + if (!string_equals(file_extension, ".json") && !string_equals(file_extension, ".xml") && !string_equals(file_extension, ".yaml")) + { + platform_show_message(main_window, localize("invalid_search_result_file"), localize("error_importing_results")); + return; + } + + scroll_y = 0; + file_content content = platform_read_file_content(path_buf, "r"); + + if (!content.content || content.file_error) + { + platform_destroy_file_content(&content); + return; + } + + search_result *new_result = create_empty_search_result(); + search_result *old_result = current_search_result; + current_search_result = new_result; + + thread cleanup_thread = thread_start(destroy_search_result_thread, old_result); + thread_detach(&cleanup_thread); + + if (string_equals(file_extension, ".json")) + { + bool result = read_json_file(content.content, content.content_length, new_result); + if (!result) goto failed_to_load_file; + } + else + { + goto failed_to_load_file; + } + + new_result->walking_file_system = false; + new_result->done_finding_matches = true; + new_result->done_finding_files = true; + + snprintf(global_status_bar.result_status_text, MAX_INPUT_LENGTH, localize("files_matches_comparison"), current_search_result->matches.length, current_search_result->files_searched, current_search_result->find_duration_us/1000.0); + + array_destroy(&new_result->files); + platform_destroy_file_content(&content); + return; + + failed_to_load_file: + platform_show_message(main_window, localize("invalid_search_result_file"), localize("error_importing_results")); + platform_destroy_file_content(&content); +} + +static void* import_results_d(void *arg) +{ + char path_buf[MAX_INPUT_LENGTH]; + path_buf[0] = 0; + + char start_path[MAX_INPUT_LENGTH]; + snprintf(start_path, MAX_INPUT_LENGTH, "%s%s", binary_path, ""); + + char default_save_file_extension[50]; + string_copyn(default_save_file_extension, "json", 50); + + struct open_dialog_args *args = mem_alloc(sizeof(struct open_dialog_args)); + args->buffer = path_buf; + args->type = OPEN_FILE; + args->file_filter = SEARCH_RESULT_FILE_EXTENSION; + args->start_path = start_path; + args->default_save_file_extension = default_save_file_extension; + + platform_open_file_dialog_block(args); + + if (string_equals(path_buf, "")) return 0; + if (!platform_file_exists(path_buf)) return 0; + + import_results_from_file(path_buf); + return 0; +} + +void import_results() +{ + thread thr; + thr.valid = false; + + while (!thr.valid) + thr = thread_start(import_results_d, 0); + thread_detach(&thr); +}
\ No newline at end of file diff --git a/src/save.h b/src/save.h new file mode 100644 index 0000000..0772daa --- /dev/null +++ b/src/save.h @@ -0,0 +1,16 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_SAVE +#define INCLUDE_SAVE + +#define SEARCH_RESULT_FILE_EXTENSION "*.json" + +bool export_results(search_result *result); +void import_results_from_file(char *path_buf); +void import_results(); + +#endif
\ No newline at end of file diff --git a/src/settings.c b/src/settings.c new file mode 100644 index 0000000..b379470 --- /dev/null +++ b/src/settings.c @@ -0,0 +1,313 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +void reset_status_text(); +void set_status_text_to_finished_search(); + +void settings_page_create() +{ + global_settings_page.active = false; + global_settings_page.font_small = assets_load_font(_binary____data_fonts_mono_ttf_start, + _binary____data_fonts_mono_ttf_end, 16); + global_settings_page.logo_img = assets_load_image(_binary____data_imgs_logo_64_png_start, + _binary____data_imgs_logo_64_png_end, true); + global_settings_page.keyboard = keyboard_input_create(); + global_settings_page.mouse = mouse_input_create(); + + camera cam; + cam.x = 0; + cam.y = 0; + cam.rotation = 0; + + global_settings_page.camera = cam; + + global_settings_page.btn_close = ui_create_button(); + global_settings_page.btn_save = ui_create_button(); + global_settings_page.dropdown_language = ui_create_dropdown(); + global_settings_page.dropdown_doubleclick = ui_create_dropdown(); + global_settings_page.textbox_max_file_size = ui_create_textbox(9); + global_settings_page.textbox_max_thread_count = ui_create_textbox(5); + global_settings_page.checkbox_parallelize_search = ui_create_checkbox(false); +} + +static void load_current_settings_into_ui() +{ + if (global_settings_page.max_thread_count != 0) + snprintf(global_settings_page.textbox_max_thread_count.buffer, global_settings_page.textbox_max_thread_count.max_len, "%d",global_settings_page.max_thread_count); + + if (global_settings_page.max_file_size != 0) + snprintf(global_settings_page.textbox_max_file_size.buffer, global_settings_page.textbox_max_file_size.max_len, "%d", global_settings_page.max_file_size); +} + +void settings_page_update_render() +{ + if (global_settings_page.active) + { + platform_window_make_current(&global_settings_page.window); + platform_handle_events(&global_settings_page.window, &global_settings_page.mouse, &global_settings_page.keyboard); + platform_set_cursor(&global_settings_page.window, CURSOR_DEFAULT); + + render_clear(); + + camera_apply_transformations(&global_settings_page.window, &global_settings_page.camera); + + global_ui_context.layout.active_window = &global_settings_page.window; + global_ui_context.keyboard = &global_settings_page.keyboard; + global_ui_context.mouse = &global_settings_page.mouse; + + ui_begin(3); + { + ui_begin_menu_bar(); + { + if (ui_push_menu(localize("general"))) + { + global_settings_page.selected_tab_index = 0; + } + if (ui_push_menu(localize("interface"))) + { + global_settings_page.selected_tab_index = 1; + } + } + ui_end_menu_bar(); + + ui_push_separator(); + + if (global_settings_page.selected_tab_index == 0) + { + ///////////////////////////////////// + // max file size + ///////////////////////////////////// + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_text(localize("max_file_size")); + ui_push_text(localize("zero_for_no_limit")); + } + ui_block_end(); + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_textbox(&global_settings_page.textbox_max_file_size, "0")) + { + keyboard_set_input_mode(&global_settings_page.keyboard, INPUT_NUMERIC); + } + ui_push_text("KB"); + } + ui_block_end(); + + ///////////////////////////////////// + // max threads + ///////////////////////////////////// + global_ui_context.layout.offset_y += 10; + + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_text(localize("max_threads")); + ui_push_text(localize("minimum_of_1")); + } + ui_block_end(); + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_textbox(&global_settings_page.textbox_max_thread_count, "0")) + { + keyboard_set_input_mode(&global_settings_page.keyboard, INPUT_NUMERIC); + } + ui_push_text("Threads"); + + } + ui_block_end(); + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_hypertext_link(localize("copy_config_path"))) + { + char buffer[PATH_MAX]; + platform_set_clipboard(main_window, get_config_save_location(buffer)); + } + } + ui_block_end(); + } + else if (global_settings_page.selected_tab_index == 1) + { + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_text(localize("language")); + } + ui_block_end(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_dropdown(&global_settings_page.dropdown_language, locale_get_name())) + { + for (s32 i = 0; i < global_localization.mo_files.length; i++) + { + mo_file *file = array_at(&global_localization.mo_files, i); + + if (ui_push_dropdown_item(file->icon, file->locale_full, i)) + { + set_locale(file->locale_id); + } + } + } + } + ui_block_end(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_text(localize("double_click_action")); + } + ui_block_end(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + char* available_options[OPTION_RESULT+1] = { + localize("double_click_action_1"), + localize("double_click_action_2"), + localize("double_click_action_3"), + localize("double_click_action_4"), + }; + + if (ui_push_dropdown(&global_settings_page.dropdown_doubleclick, available_options[global_settings_page.current_double_click_selection_option])) + { + for (s32 i = 0; i < OPTION_RESULT+1; i++) + { + if (ui_push_dropdown_item(0, available_options[i], i)) + { + global_settings_page.current_double_click_selection_option = i; + } + } + } + } + ui_block_end(); + +#if 0 + ui_block_begin(LAYOUT_HORIZONTAL); + { + ui_push_text("Style"); + } + ui_block_end(); + + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_color_button("Light", global_ui_context.style.id == UI_STYLE_LIGHT, rgb(250, 250, 250))) + { + ui_set_style(UI_STYLE_LIGHT); + } + if (ui_push_color_button("Dark", global_ui_context.style.id == UI_STYLE_DARK, rgb(50, 50, 50))) + { + ui_set_style(UI_STYLE_DARK); + } + } + ui_block_end(); +#endif + } + + global_ui_context.layout.offset_y = global_settings_page.window.height - 33; + + ui_block_begin(LAYOUT_HORIZONTAL); + { + if (ui_push_button(&global_settings_page.btn_close, localize("close"))) + { + global_settings_page.textbox_max_thread_count.buffer[0] = 0; + global_settings_page.textbox_max_file_size.buffer[0] = 0; + ui_set_style(global_settings_page.current_style); + global_settings_page.current_double_click_selection_option = global_settings_page.selected_double_click_selection_option; + global_settings_page.active = false; + set_locale(global_settings_page.current_locale_id); + settings_page_hide(); + + return; + } + if (ui_push_button(&global_settings_page.btn_close, localize("save"))) + { + global_settings_page.current_style = global_ui_context.style.id; + global_settings_page.max_thread_count = string_to_s32(global_settings_page.textbox_max_thread_count.buffer); + if (global_settings_page.max_thread_count < 1) + global_settings_page.max_thread_count = DEFAULT_THREAD_COUNT; + + global_settings_page.max_file_size = string_to_s32(global_settings_page.textbox_max_file_size.buffer); + + global_settings_page.textbox_max_thread_count.buffer[0] = 0; + global_settings_page.textbox_max_file_size.buffer[0] = 0; + global_settings_page.selected_double_click_selection_option = global_settings_page.current_double_click_selection_option; + + global_settings_page.active = false; + settings_page_hide(); + return; + } + } + ui_block_end(); + } + ui_end(); + + if (!global_settings_page.window.is_open) + { + global_settings_page.active = false; + settings_page_hide(); + return; + } + + platform_window_swap_buffers(&global_settings_page.window); + } +} + +void settings_page_show() +{ + if (platform_window_is_valid(&global_settings_page.window)) return; + + load_current_settings_into_ui(); + + global_settings_page.window = platform_open_window(localize("text_search_settings"), + 450, 280, 450, 280, 450, 280); + + settings_window = &global_settings_page.window; + + global_settings_page.active = true; + global_settings_page.selected_tab_index = 0; + global_settings_page.current_locale_id = locale_get_id(); + global_settings_page.current_double_click_selection_option = global_settings_page.selected_double_click_selection_option; + + platform_set_icon(&global_settings_page.window, global_settings_page.logo_img); +} + +void settings_page_hide() +{ + if (platform_window_is_valid(&global_settings_page.window)) + { + settings_window = 0; + + platform_destroy_window(&global_settings_page.window); + + global_settings_page.btn_close.state = false; + global_settings_page.btn_save.state = false; + global_settings_page.active = false; + + global_settings_page.mouse.x = -1; + global_settings_page.mouse.y = -1; + } +} + +void settings_page_hide_without_save() +{ + if (platform_window_is_valid(&global_settings_page.window)) + { + global_settings_page.textbox_max_thread_count.buffer[0] = 0; + global_settings_page.textbox_max_file_size.buffer[0] = 0; + + global_settings_page.active = false; + set_locale(global_settings_page.current_locale_id); + settings_page_hide(); + } +} + +void settings_page_destroy() +{ + keyboard_input_destroy(&global_settings_page.keyboard); + ui_destroy_textbox(&global_settings_page.textbox_max_file_size); + ui_destroy_textbox(&global_settings_page.textbox_max_thread_count); + assets_destroy_font(global_settings_page.font_small); + assets_destroy_image(global_settings_page.logo_img); + + if (platform_window_is_valid(&global_settings_page.window)) + platform_destroy_window(&global_settings_page.window); +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..11ab307 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,60 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_SETTINGS +#define INCLUDE_SETTINGS + +typedef enum t_double_click_option +{ + OPTION_PATH, + OPTION_PATH_LINE, + OPTION_PATH_LINE_FILTER, + OPTION_RESULT, +} double_click_option; + +typedef struct t_settings_page +{ + platform_window window; + keyboard_input keyboard; + mouse_input mouse; + camera camera; + bool active; + + font *font_small; + image *logo_img; + + button_state btn_close; + button_state btn_save; + dropdown_state dropdown_language; + dropdown_state dropdown_doubleclick; + textbox_state textbox_max_file_size; + textbox_state textbox_max_thread_count; + checkbox_state checkbox_parallelize_search; + s32 selected_tab_index; + + char *current_locale_id; + s32 max_thread_count; + s32 max_file_size; + u16 current_style; + u16 selected_double_click_selection_option; // saved state + u16 current_double_click_selection_option; // unsaved state +} settings_page; + +#define DEFAULT_THREAD_COUNT 10 +#define DEFAULT_MAX_FILE_SIZE 0 +#define DEFAULT_RECURSIVE_STATE 1 +#define DEFAULT_STYLE 1 + +settings_page global_settings_page; + +void settings_page_create(); +void settings_page_hide_without_save(); +void settings_page_update_render(); +void settings_page_show(); +void settings_page_hide(); +void settings_page_destroy(); + +#endif
\ No newline at end of file diff --git a/src/settings_config.c b/src/settings_config.c new file mode 100644 index 0000000..d2bc018 --- /dev/null +++ b/src/settings_config.c @@ -0,0 +1,240 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +void settings_config_write_to_file(settings_config *config, char *path) +{ + // @hardcoded + s32 len = kilobytes(20); + char *buffer = mem_alloc(len); + buffer[0] = 0; + + for (s32 i = 0; i < config->settings.length; i++) + { + config_setting *setting = array_at(&config->settings, i); + + char entry_buf[MAX_INPUT_LENGTH]; + snprintf(entry_buf, MAX_INPUT_LENGTH, "%s = \"%s\"\n", setting->name, setting->value); + string_appendn(buffer, entry_buf, MAX_INPUT_LENGTH); + } + + set_active_directory(binary_path); + platform_write_file_content(path, "w+", buffer, strlen(buffer)); + mem_free(buffer); +} + +static void get_config_from_string(settings_config *config, char *string) +{ + config_setting current_entry; + current_entry.name = 0; + current_entry.value = 0; + + s32 len = 0; + bool in_literal = false; + char *original_string = string; + + while(*string) + { + + // property name + if (*string == ' ' && !current_entry.name) + { + current_entry.name = mem_alloc(len+1); + string_copyn(current_entry.name, string-len, len); + current_entry.name[len] = 0; + string_trim(current_entry.name); + } + + // property value + if (*string == '"' && (*(string+1) == 0 || !in_literal)) + { + in_literal = !in_literal; + + if (in_literal) + { + len = -1; + } + else + { + current_entry.value = mem_alloc(len+1); + string_copyn(current_entry.value, string-len, len); + current_entry.value[len] = 0; + string_trim(current_entry.value); + } + } + + ++len; + ++string; + } + + array_push(&config->settings, ¤t_entry); +} + +static void convert_crlf_to_lf(char *buffer) +{ + char *buffer_original = buffer; + + int write_offset = 0; + int read_offset = 0; + + while(buffer[read_offset]) + { + if (buffer[read_offset] != 0x0D) + { + buffer_original[write_offset] = buffer[read_offset]; + + ++write_offset; + } + + ++read_offset; + } +} + +settings_config settings_config_load_from_file(char *path) +{ + settings_config config; + config.settings = array_create(sizeof(config_setting)); + + set_active_directory(binary_path); + + file_content content = platform_read_file_content(path, "rb"); + + if (!content.content || content.file_error) + { + platform_destroy_file_content(&content); + return config; + } + + convert_crlf_to_lf(content.content); + + s32 token_offset = 0; + for (s32 i = 0; i < content.content_length; i++) + { + char ch = ((char*)content.content)[i]; + char prev_ch = i-1 > 0 ? ((char*)content.content)[i-1] : 255; + + // end of line [lf] + if (ch == 0x0A && prev_ch != 0x0D) + { + char line[MAX_INPUT_LENGTH]; + + s32 line_len = i - token_offset; + snprintf(line, MAX_INPUT_LENGTH, "%.*s", line_len, (char*)content.content+token_offset); + token_offset = i + 1; + + get_config_from_string(&config, line); + } + } + + platform_destroy_file_content(&content); + + return config; +} + +config_setting* settings_config_get_setting(settings_config *config, char *name) +{ + for (s32 i = 0; i < config->settings.length; i++) + { + config_setting *setting = array_at(&config->settings, i); + if (setting && setting->name && name && strcmp(setting->name, name) == 0) + { + return setting; + } + } + return 0; +} + +char* settings_config_get_string(settings_config *config, char *name) +{ + config_setting* setting = settings_config_get_setting(config, name); + if (setting) + return setting->value; + else + return 0; +} + +s64 settings_config_get_number(settings_config *config, char *name) +{ + config_setting* setting = settings_config_get_setting(config, name); + if (setting && setting->value) + return string_to_u64(setting->value); + else + return 0; +} + +void settings_config_set_string(settings_config *config, char *name, char *value) +{ + config_setting* setting = settings_config_get_setting(config, name); + if (setting) + { + s32 len = strlen(value); + mem_free(setting->value); + setting->value = mem_alloc(len+1); + string_copyn(setting->value, value, len+1); + } + else + { + config_setting new_entry; + new_entry.name = 0; + + // name + s32 len = strlen(name); + new_entry.name = mem_alloc(len+1); + string_copyn(new_entry.name, name, len+1); + + // value + len = strlen(value); + new_entry.value = mem_alloc(len+1); + string_copyn(new_entry.value, value, len+1); + + array_push(&config->settings, &new_entry); + } +} + +void settings_config_set_number(settings_config *config, char *name, s64 value) +{ + config_setting* setting = settings_config_get_setting(config, name); + if (setting) + { + char num_buf[20]; + snprintf(num_buf, 20, "%"PRId64"", value); + + s32 len = strlen(num_buf); + mem_free(setting->value); + setting->value = mem_alloc(len+1); + string_copyn(setting->value, num_buf, len+1); + } + else + { + config_setting new_entry; + + // name + s32 len = strlen(name); + new_entry.name = mem_alloc(len+1); + string_copyn(new_entry.name, name, len+1); + + // value + char num_buf[20]; + snprintf(num_buf, 20, "%"PRId64"", value); + + len = strlen(num_buf); + new_entry.value = mem_alloc(len+1); + string_copyn(new_entry.value, num_buf, len+1); + array_push(&config->settings, &new_entry); + } +} + +void settings_config_destroy(settings_config *config) +{ + for (s32 i = 0; i < config->settings.length; i++) + { + config_setting *entry = array_at(&config->settings, i); + + mem_free(entry->name); + mem_free(entry->value); + } + + array_destroy(&config->settings); +}
\ No newline at end of file diff --git a/src/settings_config.h b/src/settings_config.h new file mode 100644 index 0000000..1b931e1 --- /dev/null +++ b/src/settings_config.h @@ -0,0 +1,40 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_SETTINGS_CONFIG +#define INCLUDE_SETTINGS_CONFIG + +typedef struct t_config_setting +{ + char *name; + char *value; +} config_setting; + +typedef struct t_settings_config +{ + array settings; +} settings_config; + +/* Example of file: +* NAME = "Aldrik Ramaekers" +* AGE = "69" +* NUMBER = "15" +*/ + +settings_config settings_config_load_from_file(char *path); +void settings_config_write_to_file(settings_config *config, char *path); +void settings_config_destroy(settings_config *config); + +config_setting* settings_config_get_setting(settings_config *config, char *name); +char* settings_config_get_string(settings_config *config, char *name); +s64 settings_config_get_number(settings_config *config, char *name); + +void settings_config_set_string(settings_config *config, char *name, char *value); +void settings_config_set_number(settings_config *config, char *name, s64 value); + + + +#endif
\ No newline at end of file diff --git a/src/string_utils.c b/src/string_utils.c new file mode 100644 index 0000000..5c8aac9 --- /dev/null +++ b/src/string_utils.c @@ -0,0 +1,551 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +bool string_match(char *first, char *second) +{ + // If we reach at the end of both strings, we are done + if (*first == '\0' && *second == '\0') + return true; + + // Make sure that the characters after '*' are present + // in second string. This function assumes that the first + // string will not contain two consecutive '*' + if (*first == '*' && *(first+1) != '\0' && *second == '\0') + return false; + + // If the first string contains '?', or current characters + // of both strings string_match + if (*first == '?' || *first == *second) + return string_match(first+1, second+1); + + // If there is *, then there are two possibilities + // a) We consider current character of second string + // b) We ignore current character of second string. + if (*first == '*') + return string_match(first+1, second) || string_match(first, second+1); + return false; +} + +bool string_is_asteriks(char *text) +{ + utf8_int32_t ch; + while((text = utf8codepoint(text, &ch)) && ch) + { + if (ch != '*') return false; + } + return true; +} + +bool string_contains_ex(char *text_to_search, char *text_to_find, array *text_matches, bool *cancel_search) +{ + bool final_result = false; + bool is_asteriks_only = false; + + // * wildcard at the start of text to find is not needed + if (string_is_asteriks(text_to_find)) + { + is_asteriks_only = true; + text_to_find += strlen(text_to_find); + } + + // remove all asteriks from start + utf8_int32_t br; + while(utf8codepoint(text_to_find, &br) && br == '*') + { + text_to_find = utf8codepoint(text_to_find, &br); + } + + char *text_to_find_original = text_to_find; + bool save_info = (text_matches != 0); + + utf8_int32_t text_to_search_ch = 0; + utf8_int32_t text_to_find_ch = 0; + size_t text_to_find_char_len = utf8len(text_to_find); + + s32 line_nr_val = 1; + s32 word_offset_val = 0; + s32 word_match_len_val = 0; + char* line_start_ptr = text_to_search; + + s32 index = 0; + while((text_to_search = utf8codepoint(text_to_search, &text_to_search_ch)) + && text_to_search_ch) + { + if (cancel_search && *cancel_search) goto set_info_and_return_failure; + + word_offset_val++; + if (text_to_search_ch == '\n') + { + line_nr_val++; + word_offset_val = 0; + line_start_ptr = text_to_search; + } + + char *text_to_search_current_attempt = text_to_search; + utf8_int32_t text_to_search_current_attempt_ch = 0; + + bool in_wildcard = false; + + text_to_find = utf8codepoint(text_to_find, &text_to_find_ch); + text_to_search_current_attempt = utf8codepoint(text_to_search_current_attempt, + &text_to_search_current_attempt_ch); + + word_match_len_val = 0; + while(text_to_search_current_attempt_ch) + { + if (cancel_search && *cancel_search) goto set_info_and_return_failure; + + // wildcard, accept any character in text to search + if (text_to_find_ch == '?') + goto continue_search; + + // character matches, + if (text_to_find_ch == text_to_search_current_attempt_ch && in_wildcard) + in_wildcard = false; + + // wildcard, accept any characters in text to search untill next char is found + if (text_to_find_ch == '*') + { + text_to_find = utf8codepoint(text_to_find, &text_to_find_ch); + in_wildcard = true; + } + + // text to find has reached 0byte, word has been found + if (text_to_find_ch == 0) + { + if (save_info) + { + text_match new_match; + new_match.line_nr = line_nr_val; + new_match.word_offset = word_offset_val; + new_match.word_match_len = word_match_len_val; + new_match.line_start = line_start_ptr; + new_match.line_info = 0; + array_push(text_matches, &new_match); + } + + final_result = true; + + if (is_asteriks_only) + { + return final_result; + } + + break; + } + + // character does not match, continue search + if (text_to_find_ch != text_to_search_current_attempt_ch && !in_wildcard) + break; + + continue_search: + if (!in_wildcard) + text_to_find = utf8codepoint(text_to_find, &text_to_find_ch); + + text_to_search_current_attempt = utf8codepoint( + text_to_search_current_attempt, + &text_to_search_current_attempt_ch); + + word_match_len_val++; + } + + text_to_find = text_to_find_original; + index++; + } + + return final_result; + + set_info_and_return_failure: + return false; +} + +static char *ltrim(char *str, const char *seps) +{ + size_t totrim; + if (seps == NULL) { + seps = "\t\n\v\f\r "; + } + totrim = strspn(str, seps); + if (totrim > 0) { + size_t len = strlen(str); + if (totrim == len) { + str[0] = '\0'; + } + else { + memmove(str, str + totrim, len + 1 - totrim); + } + } + return str; +} + +static char *rtrim(char *str, const char *seps) +{ + int i; + if (seps == NULL) { + seps = "\t\n\v\f\r "; + } + i = strlen(str) - 1; + while (i >= 0 && strchr(seps, str[i]) != NULL) { + str[i] = '\0'; + i--; + } + return str; +} + +inline void string_trim(char *string) +{ + ltrim(rtrim(string, 0), 0); +} + +inline bool string_equals(char *first, char *second) +{ + return (strcmp(first, second) == 0); +} + +// replaces " with \" for file formats +void string_appendf(char *buffer, char *text) +{ + u32 len = strlen(buffer); + while(*text) + { + if (*text < 32) + { + buffer[len] = ' '; + len++; + text++; + continue; + } + + if (*text == '"') + { + buffer[len] = '\\'; + len++; + } + if (*text == '\\') + { + buffer[len] = '\\'; + len++; + } + + buffer[len] = *text; + len++; + text++; + } +} + +void string_copyn(char *buffer, char *text, s32 bufferlen) +{ + u32 len = 0; + while(*text && len < bufferlen) + { + buffer[len] = *text; + len++; + text++; + } + buffer[len] = 0; +} + +void string_appendn(char *buffer, char *text, s32 bufferlen) +{ + u32 len = strlen(buffer); + while(*text && len < bufferlen) + { + buffer[len] = *text; + len++; + text++; + } + buffer[len] = 0; +} + +void string_append(char *buffer, char *text) +{ + u32 len = strlen(buffer); + while(*text) + { + buffer[len] = *text; + len++; + text++; + } + buffer[len] = 0; +} + +bool string_remove(char **buffer, char *text) +{ + s32 len = strlen(text); + char tmp[200]; + memcpy(tmp, *buffer, len); + memset(tmp+len, 0, 1); + + if (string_equals(tmp, text)) + { + *buffer += len; + return true; + } + + return false; +} + +char* string_get_json_literal(char **buffer, char *tmp) +{ + char *buf_start = *buffer; + char *buf = *buffer; + s32 len = 0; + while(*buf) + { + if ((*buf == ',' || *buf == '}') && (len > 0 && *(buf-1) == '"') && (len > 1 && *(buf-2) != '\\')) + { + memcpy(tmp, buf_start, len); + memset(tmp+len-1, 0, 1); + *buffer += len-1; + return tmp; + } + + len++; + buf++; + } + + return tmp; +} + +s32 string_get_json_ulong_number(char **buffer) +{ + char tmp[20]; + char *buf_start = *buffer; + char *buf = *buffer; + s32 len = 0; + while(*buf) + { + if (*buf == ',' || *buf == '}') + { + memcpy(tmp, buf_start, len); + memset(tmp+len, 0, 1); + *buffer += len; + return string_to_u64(tmp); + } + + len++; + buf++; + } + + return 0; +} + +s32 string_get_json_number(char **buffer) +{ + char tmp[20]; + char *buf_start = *buffer; + char *buf = *buffer; + s32 len = 0; + while(*buf) + { + if (*buf == ',' || *buf == '}') + { + memcpy(tmp, buf_start, len); + memset(tmp+len, 0, 1); + *buffer += len; + return string_to_s32(tmp); + } + + len++; + buf++; + } + + return 0; +} + +void utf8_str_remove_range(char *str, s32 from, s32 to) +{ + char *orig_str = str; + s32 i = 0; + utf8_int32_t ch = 0; + s32 total_len = strlen(str)+1+4; + char *replacement = calloc(total_len,1); + char *rep_off = replacement; + replacement[0] = 0; + + while((str = utf8codepoint(str, &ch)) && ch) + { + if (i < from || i >= to) + { + rep_off = utf8catcodepoint(rep_off, ch, 5); + } + + ++i; + } + *rep_off = 0; + + string_copyn(orig_str, replacement, MAX_INPUT_LENGTH); + mem_free(replacement); +} + +void utf8_str_remove_at(char *str, s32 at) +{ + char *orig_str = str; + s32 i = 0; + utf8_int32_t ch = 0; + s32 total_len = strlen(str)+1+4; + char *replacement = calloc(total_len,1); + char *rep_off = replacement; + replacement[0] = 0; + + while((str = utf8codepoint(str, &ch)) && ch) + { + if (at != i) + { + rep_off = utf8catcodepoint(rep_off, ch, 5); + } + + ++i; + } + *rep_off = 0; + + string_copyn(orig_str, replacement, MAX_INPUT_LENGTH); + mem_free(replacement); +} + +void utf8_str_insert_utf8str(char *str, s32 at, char *toinsert) +{ + s32 index = 0; + utf8_int32_t ch; + while((toinsert = utf8codepoint(toinsert, &ch)) && ch) + { + utf8_str_insert_at(str, at+index, ch); + index++; + } +} + +void utf8_str_insert_at(char *str, s32 at, utf8_int32_t newval) +{ + char *orig_str = str; + s32 i = 0; + utf8_int32_t ch = 0; + s32 total_len = strlen(str)+1+4; + char *replacement = calloc(total_len,1); + char *rep_off = replacement; + replacement[0] = 0; + + while((str = utf8codepoint(str, &ch))) + { + if (at == i) + { + rep_off = utf8catcodepoint(rep_off, newval, 5); + } + + rep_off = utf8catcodepoint(rep_off, ch, 5); + + ++i; + + if (!ch) break; + } + *rep_off = 0; + + string_copyn(orig_str, replacement, MAX_INPUT_LENGTH); + mem_free(replacement); +} + +char *utf8_str_copy_upto(char *str, s32 roof, char *buffer) +{ + utf8_int32_t ch = 0; + s32 index = 0; + char *orig_buffer = buffer; + while((str = utf8codepoint(str, &ch)) && ch) + { + if (index == roof) break; + buffer = utf8catcodepoint(buffer, ch, 5); + index++; + } + buffer = utf8catcodepoint(buffer, 0, 5); + + return orig_buffer; +} + +char *utf8_str_copy_range(char *str, s32 floor, s32 roof, char *buffer) +{ + utf8_int32_t ch = 0; + s32 index = 0; + char *orig_buffer = buffer; + while((str = utf8codepoint(str, &ch)) && ch) + { + if (index == roof) break; + if (index >= floor) + buffer = utf8catcodepoint(buffer, ch, 5); + index++; + } + buffer = utf8catcodepoint(buffer, 0, 5); + + return orig_buffer; +} + +void utf8_str_replace_at(char *str, s32 at, utf8_int32_t newval) +{ + char *orig_str = str; + s32 i = 0; + utf8_int32_t ch = 0; + s32 total_len = strlen(str)+1+4; + char *replacement = calloc(total_len,1); + char *rep_off = replacement; + replacement[0] = 0; + + while((str = utf8codepoint(str, &ch)) && ch) + { + if (at == i) + { + rep_off = utf8catcodepoint(rep_off, newval, 5); + } + else + { + rep_off = utf8catcodepoint(rep_off, ch, 5); + } + ++i; + } + *rep_off = 0; + + string_copyn(orig_str, replacement, MAX_INPUT_LENGTH); + mem_free(replacement); +} + +char* utf8_str_upto(char *str, s32 index) +{ + s32 i = 0; + utf8_int32_t ch; + char *prev_str = str; + while((str = utf8codepoint(str, &ch)) && ch) + { + if (index == i) return prev_str; + prev_str = str; + ++i; + } + + return str; +} + +utf8_int32_t utf8_str_at(char *str, s32 index) +{ + s32 i = 0; + utf8_int32_t ch; + while((str = utf8codepoint(str, &ch)) && ch) + { + if (index == i) return ch; + + ++i; + } + + return 0; +} + +bool is_string_numeric(char *str) +{ + utf8_int32_t ch; + while((str = utf8codepoint(str, &ch)) && ch) + { + if (!(ch >= 48 && ch <= 57)) + { + return false; + } + } + + return true; +}
\ No newline at end of file diff --git a/src/string_utils.h b/src/string_utils.h new file mode 100644 index 0000000..2b5c85f --- /dev/null +++ b/src/string_utils.h @@ -0,0 +1,87 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_STRING_UTILS +#define INCLUDE_STRING_UTILS + +#if 0 +printf("[lll][llll*] : %d\n", string_contains("lll", "llll*")); +printf("[lllll][l*lop] : %d\n", string_contains("lllll", "l*lop")); +printf("[22lllll][l*l] : %d\n", string_contains("22lllll", "l*l")); + +printf("[hello world][h?lo] : %d\n", string_contains("hello world", "h?lo")); +printf("[\"hello sailor\"][sailor] : %d\n", string_contains(" wsdf asd \"hello sailor\" asdf asdf ", "sailor")); +printf("[\"hello sailor\"][*sailor] : %d\n", string_contains(" wsdf asd \"hello sailor\" asdf asdf ", "*sailor")); +printf("\n"); + +printf("[\"hello sailor\"][*sailor\"] : %d\n", string_contains(" wsdf asd \"hello sailor\" asdf asdf ", "*sailor\"")); +printf("[\"hello sailor\"][*sailor*] : %d\n", string_contains(" wsdf asd \"hello sailor\" asdf asdf ", "*sailor*")); +printf("[\"hello sailor\"][sailor*] : %d\n", string_contains(" wsdf asd \"hello sailor\" asdf asdf ", "sailor*")); +printf("[22lllll pi23hjp rbksje LSKJDh l][LS*] : %d\n", + string_contains("22lllll pi23hjp rbksje LSKJDh l", "LS*")); +printf("[22lllll lal][l*l] : %d\n", string_contains("22lllll lal", "l*l")); +printf("[22lllll][*l*l] : %d\n", string_contains("lllll", "*l*l")); +printf("[hello world][hello] : %d\n", string_contains("hello world", "hello")); +printf("[hello world][h?llo] : %d\n", string_contains("hello world", "h?llo")); +printf("[hello world][h????] : %d\n", string_contains("hello world", "h????")); +printf("[hello world][h*lo] : %d\n", string_contains("hello world", "h*lo")); +printf("[hello world][*] : %d\n", string_contains("hello world", "*")); +printf("[hello world][h*] : %d\n", string_contains("hello world", "h*")); +printf("[hello world][*o] : %d\n", string_contains("hello world", "*o")); +printf("[hello world][h*o] : %d\n", string_contains("hello world", "h*o")); +printf("[hello world][*lo] : %d\n", string_contains("hello world", "*lo")); +printf("[hello world][hel*lo] : %d\n", string_contains("hello world", "hel*lo")); + +printf("[lllll][l*l] : %d\n", string_contains("lllll", "l*l")); +printf("[llllllll][l*llll] : %d\n", string_contains("lllll", "l*llll")); +printf("[llllllll][l*lll] : %d\n", string_contains("lllll", "l*lll")); +printf("[llllllll][llll*l] : %d\n", string_contains("lllll", "llll*l")); +printf("[llllllll][*] : %d\n", string_contains("lllll", "*")); +printf("[lllll][l?lll] : %d\n", string_contains("lllll", "l?lll")); + +printf("[lllll][lllll] : %d\n", string_contains("lllll", "lllll")); +printf("[lllll][*llll] : %d\n", string_contains("lllll", "*llll")); +printf("[lllll][llll*] : %d\n", string_contains("lllll", "llll*")); +printf("[lllll][*llll*] : %d\n", string_contains("lllll", "*llll*")); +printf("[lllll][*lllll*] : %d\n", string_contains("lllll", "*lllll*")); +printf("[lllll][*ll*] : %d\n", string_contains("lllll", "*ll*")); +#endif + +typedef struct t_text_match +{ + u32 line_nr; + s32 word_offset; + s32 word_match_len; + char *line_start; + char *line_info; +} text_match; + +#define string_contains(big, small) string_contains_ex(big, small, 0, 0) +bool string_match(char *first, char *second); +bool string_contains_ex(char *big, char *small, array *text_matches, bool *cancel_search); +void string_trim(char *string); +bool string_equals(char *first, char *second); +void string_append(char *buffer, char *text); +void string_copyn(char *buffer, char *text, s32 bufferlen); +void string_appendn(char *buffer, char *text, s32 bufferlen); +void string_appendf(char *buffer, char *text); +bool string_remove(char **buffer, char *text); +char* string_get_json_literal(char **buffer, char *tmp); +s32 string_get_json_number(char **buffer); +s32 string_get_json_ulong_number(char **buffer); + +utf8_int32_t utf8_str_at(char *str, s32 index); +void utf8_str_remove_at(char *str, s32 at); +void utf8_str_remove_range(char *str, s32 from, s32 to); +void utf8_str_insert_at(char *str, s32 at, utf8_int32_t newval); +void utf8_str_insert_utf8str(char *str, s32 at, char *toinsert); +void utf8_str_replace_at(char *str, s32 at, utf8_int32_t newval); +char* utf8_str_upto(char *str, s32 index); +char *utf8_str_copy_upto(char *str, s32 roof, char *buffer); +char *utf8_str_copy_range(char *str, s32 floor, s32 roof, char *buffer); +bool is_string_numeric(char *str); + +#endif
\ No newline at end of file diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..b019fdb --- /dev/null +++ b/src/thread.h @@ -0,0 +1,67 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_THREAD +#define INCLUDE_THREAD + +#ifdef OS_LINUX +#include <pthread.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> + +struct t_thread +{ + pthread_t thread; + bool valid; +}; + +struct t_mutex +{ + pthread_mutex_t mutex; +}; +#endif + +#ifdef OS_WIN +#include <windows.h> +#include <process.h> /* _beginthread, _endthread */ +#include <stddef.h> +#include <stdlib.h> +#include <conio.h> + +struct t_thread +{ + HANDLE thread; + bool valid; +}; + +struct t_mutex +{ + HANDLE mutex; +}; +#endif + +typedef struct t_thread thread; +typedef struct t_mutex mutex; + +thread thread_start(void *(*start_routine) (void *), void *arg); +void thread_join(thread *thread); +bool thread_tryjoin(thread *thread); +void thread_detach(thread *thread); +void thread_stop(thread *thread); +u32 thread_get_id(); +void thread_sleep(u64 microseconds); + +mutex mutex_create_recursive(); +mutex mutex_create(); + +void mutex_lock(mutex *mutex); +bool mutex_trylock(mutex *mutex); +void mutex_unlock(mutex *mutex); + +void mutex_destroy(mutex *mutex); + +#endif
\ No newline at end of file diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..e18052f --- /dev/null +++ b/src/ui.c @@ -0,0 +1,1481 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +inline void ui_begin(s32 id) +{ + global_ui_context.item_hovered = false; + global_ui_context.next_id = id * 100; + global_ui_context.layout.offset_x = 0; + global_ui_context.layout.offset_y = 0; + global_ui_context.layout.width = global_ui_context.layout.active_window->width; + global_ui_context.layout.height = global_ui_context.layout.active_window->height; + global_ui_context.layout.active_dropdown_state = 0; +} + +inline void ui_end() +{ + +} + +inline checkbox_state ui_create_checkbox(bool selected) +{ + checkbox_state state; + state.state = selected; + + return state; +} + +inline textbox_state ui_create_textbox(u16 max_len) +{ + assert(max_len > 0); + assert(max_len <= MAX_INPUT_LENGTH); + + textbox_state state; + state.max_len = max_len; + state.buffer = mem_alloc(max_len+1); + state.buffer[0] = 0; + state.state = false; + state.text_offset_x = 0; + state.history = array_create(sizeof(textbox_history_entry)); + state.future = array_create(sizeof(textbox_history_entry)); + array_reserve(&state.history, 100); + state.history.reserve_jump = 100; + array_reserve(&state.future, 100); + state.future.reserve_jump = 100; + state.selection_start_index = 0; + state.double_clicked_to_select = false; + state.double_clicked_to_select_cursor_index = 0; + state.diff = 0; + state.last_click_cursor_index = -1; + state.attempting_to_select = false; + state.deselect_on_enter = true; + + return state; +} + +void ui_destroy_textbox(textbox_state *state) +{ + for (s32 i = 0; i < state->history.length; i++) + { + char **history_entry = array_at(&state->history, i); + mem_free(*history_entry); + } + array_destroy(&state->history); + array_destroy(&state->future); + + mem_free(state->buffer); +} + +inline button_state ui_create_button() +{ + button_state state; + state.state = 0; + + return state; +} + +inline scroll_state ui_create_scroll(s32 scroll) +{ + scroll_state state; + state.scroll = 0; + state.height = scroll; + + return state; +} + +inline dropdown_state ui_create_dropdown() +{ + dropdown_state state; + state.state = 0; + state.selected_index = 0; + return state; +} + +void ui_set_style(u16 style) +{ + global_ui_context.style.id = style; + if (style == UI_STYLE_LIGHT) + { + global_ui_context.style.hypertext_foreground = rgb(66, 134, 244); + global_ui_context.style.hypertext_hover_foreground = rgb(221, 93, 202); + global_ui_context.style.image_outline_tint = rgb(200,200,200); + global_ui_context.style.scrollbar_handle_background = rgb(225,225,225); + global_ui_context.style.info_bar_background = rgb(225,225,225); + global_ui_context.style.error_foreground = rgb(224,79,95); + global_ui_context.style.item_hover_background = rgb(240,220,220); + global_ui_context.style.scrollbar_background = rgb(255,255,255); + global_ui_context.style.background = rgb(255,255,255); + global_ui_context.style.menu_hover_background = rgb(200,200,200); + global_ui_context.style.menu_background = rgb(225,225,225); + global_ui_context.style.widget_hover_background = rgb(200,200,200); + global_ui_context.style.widget_background = rgb(225,225,225); + global_ui_context.style.border = rgb(180,180,180); + global_ui_context.style.foreground = rgb(10, 10, 10); + global_ui_context.style.textbox_background = rgb(240,240,240); + global_ui_context.style.textbox_foreground = rgb(10,10,10); + global_ui_context.style.textbox_placeholder_foreground = rgb(80,80,80); + global_ui_context.style.textbox_active_border = rgb(66, 134, 244); + } + if (style == UI_STYLE_DARK) + { + global_ui_context.style.hypertext_foreground = rgb(66, 134, 244); + global_ui_context.style.hypertext_hover_foreground = rgb(221, 93, 202); + global_ui_context.style.scrollbar_handle_background = rgb(50,50,50); + global_ui_context.style.menu_hover_background = rgb(60,60,60); + global_ui_context.style.item_hover_background = rgb(80,60,60); + global_ui_context.style.image_outline_tint = rgb(200,200,200); + global_ui_context.style.error_foreground = rgb(224,79,95); + global_ui_context.style.scrollbar_background = rgb(80,80,80); + global_ui_context.style.widget_hover_background = rgb(50,50,50); + global_ui_context.style.widget_background = rgb(65,65,65); + global_ui_context.style.info_bar_background = rgb(65,65,65); + global_ui_context.style.menu_background = rgb(65,65,65); + global_ui_context.style.background = rgb(80, 80, 80); + global_ui_context.style.border = rgb(60,60,60); + global_ui_context.style.foreground = rgb(240,240,240); + global_ui_context.style.textbox_background = rgb(65,65,65); + global_ui_context.style.textbox_foreground = rgb(240, 240,240); + global_ui_context.style.textbox_active_border = rgb(66, 134, 244); + } +} + +static scroll_state empty_scroll; +inline void ui_create(platform_window *window, keyboard_input *keyboard, mouse_input *mouse, camera *camera, font *font_small) +{ + ui_set_style(UI_STYLE_LIGHT); + + global_ui_context.layout.layout_direction = LAYOUT_VERTICAL; + global_ui_context.layout.offset_x = 0; + global_ui_context.layout.offset_y = 0; + global_ui_context.layout.active_window = window; + global_ui_context.layout.width = global_ui_context.layout.active_window->width; + empty_scroll = ui_create_scroll(1); + global_ui_context.layout.scroll = &empty_scroll; + + global_ui_context.keyboard = keyboard; + global_ui_context.mouse = mouse; + global_ui_context.font_small = font_small; + global_ui_context.active_menus = array_create(sizeof(s32)); + global_ui_context.menu_item_count = 0; + global_ui_context.camera = camera; + + array_reserve(&global_ui_context.active_menus, 100); +} + +static void ui_pop_scissor() +{ + if (global_ui_context.layout.scroll->in_scroll) + { + render_set_scissor(global_ui_context.layout.active_window, + global_ui_context.layout.offset_x, + global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING + 1, + global_ui_context.layout.width, + global_ui_context.layout.scroll->height + WIDGET_PADDING - 3); + } + else + { + render_reset_scissor(); + } +} + +inline void ui_block_begin(layout_direction direction) +{ + global_ui_context.layout.layout_direction = direction; + global_ui_context.layout.block_height = 0; + global_ui_context.layout.start_offset_y = global_ui_context.layout.offset_y; + global_ui_context.layout.start_offset_x = global_ui_context.layout.offset_x; + + ui_pop_scissor(); +} + +inline void ui_block_end() +{ + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + { + global_ui_context.layout.offset_y += global_ui_context.layout.block_height + WIDGET_PADDING; + } + global_ui_context.layout.offset_x = global_ui_context.layout.start_offset_x; +} + +inline void ui_set_active_window(platform_window *window) +{ + global_ui_context.layout.active_window = window; +} + +inline void ui_begin_menu_bar() +{ + s32 w = global_ui_context.layout.width; + s32 h = global_ui_context.layout.active_window->height; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y; + + global_ui_context.layout.offset_x = 0; + global_ui_context.layout.layout_direction = LAYOUT_HORIZONTAL; + + render_rectangle(0, y, w, MENU_BAR_HEIGHT, global_ui_context.style.menu_background); + render_rectangle(0, y, w, 1, global_ui_context.style.border); + global_ui_context.layout.menu_offset_y = 0; +} + +inline bool ui_is_menu_active(u32 id) +{ + for (int i = 0; i < global_ui_context.active_menus.length; i++) + { + s32 *iid = array_at(&global_ui_context.active_menus, i); + if (*iid == id) return true; + } + return false; +} + +inline u32 ui_get_id() +{ + return global_ui_context.next_id++; +} + +inline void ui_push_separator() +{ + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y; + s32 w = global_ui_context.layout.width; + + render_rectangle(x, y, w, 1, global_ui_context.style.border); + global_ui_context.layout.offset_y += 1 + WIDGET_PADDING; +} + +void ui_push_vertical_dragbar() +{ + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x + global_ui_context.layout.width; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y - WIDGET_PADDING; + s32 h = global_ui_context.layout.height; + + render_rectangle(x, y, 5, h, global_ui_context.style.border); +} + +inline void ui_push_menu_item_separator() +{ + global_ui_context.layout.menu_offset_y += 1; +} + +static s32 ui_get_scroll() +{ + if (global_ui_context.layout.scroll->in_scroll) + { + return global_ui_context.layout.scroll->scroll; + } + + return 0; +} + +bool ui_push_color_button(char *text, bool selected, color c) +{ + bool result = false; + + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 text_x = x + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 text_y = y + (BUTTON_HEIGHT/2) - (global_ui_context.font_small->px_h/2); + s32 total_w = + BUTTON_HORIZONTAL_TEXT_PADDING + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + s32 h = BUTTON_HEIGHT; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + color bg_color = c; + + s32 virt_top = y; + s32 virt_bottom = y + h; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y >= virt_top && mouse_y < virt_bottom && !global_ui_context.item_hovered) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + bg_color.r-=20; + bg_color.g-=20; + bg_color.b-=20; + + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + result = true; + } + } + + if (selected) + { + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 4, global_ui_context.style.border); + } + else + { + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 1, global_ui_context.style.border); + } + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; + + return result; +} + +bool ui_push_dropdown_item(image *icon, char *title, s32 index) +{ + bool result = false; + + set_render_depth(30); + + u32 id = ui_get_id(); + global_ui_context.layout.dropdown_item_count++; + s32 h = BUTTON_HEIGHT; + s32 x = global_ui_context.layout.dropdown_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() + ((global_ui_context.layout.dropdown_item_count)*h-(1*global_ui_context.layout.dropdown_item_count)); + s32 text_x = x + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 text_y = y + (BUTTON_HEIGHT/2) - (global_ui_context.font_small->px_h / 2); + s32 total_w = DROPDOWN_ITEM_WIDTH + + BUTTON_HORIZONTAL_TEXT_PADDING + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + color bg_color = global_ui_context.style.widget_background; + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y > y && mouse_y < y + h) + { + global_ui_context.item_hovered = true; + + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.layout.active_dropdown_state->selected_index = index; + result = true; + } + + bg_color = global_ui_context.style.widget_hover_background; + } + + + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 1, global_ui_context.style.border); + if (icon) + { + render_image(icon, x+(BUTTON_HORIZONTAL_TEXT_PADDING/2), + y + (h - (h-10))/2, h-10, h-10); + text_x += h-10; + } + render_text(global_ui_context.font_small, text_x+(BUTTON_HORIZONTAL_TEXT_PADDING/2)-5, text_y, title, global_ui_context.style.foreground); + + +#if 0 + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; +#endif + set_render_depth(1); + + return result; +} + +bool ui_push_dropdown(dropdown_state *state, char *title) +{ + bool result = false; + + global_ui_context.layout.active_dropdown_state = state; + + u32 id = ui_get_id(); + global_ui_context.layout.dropdown_item_count = 0; + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 text_x = x + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 text_y = y + (BUTTON_HEIGHT/2) - (global_ui_context.font_small->px_h/2); + s32 total_w = DROPDOWN_WIDTH + BUTTON_HORIZONTAL_TEXT_PADDING + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + s32 h = BUTTON_HEIGHT; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + color bg_color = global_ui_context.style.widget_background; + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y >= y && mouse_y < y + h && !global_ui_context.item_hovered) + { + global_ui_context.item_hovered = true; + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + if (is_left_clicked(global_ui_context.mouse)) + { + state->state = !state->state; + } + + bg_color = global_ui_context.style.widget_hover_background; + } + else if (is_left_down(global_ui_context.mouse) && state->state) + { + state->state = false; + // render dropdown this frame so item can be selected + result = true; + } + + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 1, global_ui_context.style.border); + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + + render_triangle(x+total_w - h, y+(h-(h-12))/2, h-12, h-12, global_ui_context.style.border); + global_ui_context.layout.dropdown_x = global_ui_context.layout.offset_x; + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; + + return result || state->state; +} + +bool ui_push_menu(char *title) +{ + bool result = false; + + global_ui_context.layout.menu_offset_y = 0; + global_ui_context.menu_item_count = 0; + u32 id = ui_get_id(); + + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 w = calculate_text_width(global_ui_context.font_small, title) + + (MENU_HORIZONTAL_PADDING*2); + s32 text_h = global_ui_context.font_small->px_h; + s32 h = MENU_BAR_HEIGHT-1; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y+1; + s32 text_y = global_ui_context.layout.offset_y - (text_h / 2) + (h / 2) + global_ui_context.camera->y; + s32 text_x = x + MENU_HORIZONTAL_PADDING; + + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + color bg_color = global_ui_context.style.menu_background; + + bool is_open = ui_is_menu_active(id); + result = is_open; + + if (mouse_x >= x && mouse_x < x + w && mouse_y >= y && mouse_y < y + h) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + if (is_left_clicked(global_ui_context.mouse)) + { + if (is_open) + array_remove_by(&global_ui_context.active_menus, &id); + else + array_push(&global_ui_context.active_menus, &id); + + result = !is_open; + is_open = result; + } + + bg_color = global_ui_context.style.menu_hover_background; + } + else if (is_left_down(global_ui_context.mouse)) + { + if (is_open) + array_remove_by(&global_ui_context.active_menus, &id); + is_open = false; + } + if (!global_ui_context.layout.active_window->has_focus && is_open) + { + array_remove_by(&global_ui_context.active_menus, &id); + is_open = false; + } + + render_rectangle(x, y, w, h, bg_color); + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + + global_ui_context.layout.prev_offset_x = global_ui_context.layout.offset_x; + global_ui_context.layout.offset_x += w; + + return result; +} + +static void ui_set_active_textbox(textbox_state *state) +{ + if (global_ui_context.current_active_textbox && global_ui_context.current_active_textbox != state) + { + global_ui_context.current_active_textbox->state = false; + } + global_ui_context.current_active_textbox = state; +} + +void set_active_textbox(textbox_state *textbox) +{ + ui_set_active_textbox(textbox); + keyboard_set_input_text(global_ui_context.keyboard, textbox->buffer); + textbox->state = true; + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + global_ui_context.keyboard->take_input = textbox->state; +} + +bool ui_push_textbox(textbox_state *state, char *placeholder) +{ + bool result = false; + static u64 cursor_tick = 0; + static u64 last_cursor_pos = -1; + + if (!global_ui_context.layout.active_window->has_focus) + state->state = false; + + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 text_x = x + 5; + s32 text_y = y + (TEXTBOX_HEIGHT/2) - (global_ui_context.font_small->px_h/2); + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + if (global_ui_context.layout.block_height < TEXTBOX_HEIGHT) + global_ui_context.layout.block_height = TEXTBOX_HEIGHT; + + bool has_text = state->buffer[0] != 0; + + if (!state->state) + { + render_rectangle(x, y, TEXTBOX_WIDTH, TEXTBOX_HEIGHT, global_ui_context.style.textbox_background); + render_rectangle_outline(x, y, TEXTBOX_WIDTH, TEXTBOX_HEIGHT, 1, global_ui_context.style.border); + } + else + { + cursor_tick++; + render_rectangle(x, y, TEXTBOX_WIDTH, TEXTBOX_HEIGHT, global_ui_context.style.textbox_background); + render_rectangle_outline(x, y, TEXTBOX_WIDTH, TEXTBOX_HEIGHT, 1, global_ui_context.style.textbox_active_border); + } + + s32 virt_top = y; + s32 virt_bottom = y + TEXTBOX_HEIGHT; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + ///////////////////////////////////////////// + ///////////////////////////////////////////// + ///////////////////////////////////////////// + + bool is_selecting = false; + bool clicked_to_select = false; + bool double_clicked_to_select_first = false; + bool clicked_to_set_cursor = false; + if (mouse_x >= x && mouse_x < x + TEXTBOX_WIDTH && mouse_y >= virt_top && mouse_y < virt_bottom) + { + if (is_left_double_clicked(global_ui_context.mouse) && has_text) + { + ui_set_active_textbox(state); + + global_ui_context.keyboard->selection_begin_offset = 0; + global_ui_context.keyboard->selection_length = utf8len(global_ui_context.keyboard->input_text); + global_ui_context.keyboard->has_selection = true; + state->selection_start_index = 0; + + global_ui_context.mouse->left_state &= ~MOUSE_DOUBLE_CLICK; + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + + state->double_clicked_to_select = true; + double_clicked_to_select_first = true; + } + if (is_left_clicked(global_ui_context.mouse)) + { + ui_set_active_textbox(state); + + keyboard_set_input_text(global_ui_context.keyboard, state->buffer); + cursor_tick = 0; + + if (global_ui_context.keyboard->has_selection) + { + global_ui_context.keyboard->has_selection = false; + } + + clicked_to_set_cursor = true; + + state->state = true; + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + result = true; + + global_ui_context.keyboard->take_input = state->state; + } + } + else if (is_left_clicked(global_ui_context.mouse)) + { + if (state->state) + { + global_ui_context.keyboard->has_selection = false; + } + + state->state = false; + } + + if (is_left_released(global_ui_context.mouse)) + { + state->attempting_to_select = false; + } + + if (state->state && global_ui_context.keyboard->has_selection && is_left_down(global_ui_context.mouse)) + is_selecting = true; + + if (keyboard_is_key_pressed(global_ui_context.keyboard, KEY_ENTER) && state->deselect_on_enter) + { + global_ui_context.keyboard->has_selection = false; + state->state = false; + } + + // calculate scissor rectangle + if (global_ui_context.layout.scroll->in_scroll) + { + vec4 v = render_get_scissor(); + s32 scissor_x = v.x + WIDGET_PADDING + 5; + s32 scissor_y = global_ui_context.layout.scroll->scroll_start_offset_y; + s32 scissor_w = v.w; + s32 scissor_h = global_ui_context.layout.scroll->height - 2; + + render_set_scissor(global_ui_context.layout.active_window, scissor_x, + scissor_y, scissor_w, scissor_h); + } + else + { + s32 scissor_x = x - global_ui_context.camera->x+3; + s32 scissor_y = y - global_ui_context.camera->y; + s32 scissor_w = TEXTBOX_WIDTH - 5; + s32 scissor_h = TEXTBOX_HEIGHT; + + render_set_scissor(global_ui_context.layout.active_window, + scissor_x, scissor_y, scissor_w, scissor_h); + } + + s32 cursor_text_w; + s32 cursor_x = 0; + + //if (!global_ui_context.keyboard->has_selection) + //state->diff = 0; + + // select first character on click + if (clicked_to_set_cursor) + { + global_ui_context.keyboard->cursor = calculate_cursor_position(global_ui_context.font_small, + state->buffer, mouse_x + state->diff - text_x); + + state->last_click_cursor_index = global_ui_context.keyboard->cursor; + state->attempting_to_select = true; + + global_ui_context.keyboard->selection_begin_offset = global_ui_context.keyboard->cursor; + +#if 0 + global_ui_context.keyboard->has_selection = true; + global_ui_context.keyboard->selection_begin_offset = calculate_cursor_position(global_ui_context.font_small, + state->buffer, mouse_x + state->diff - text_x); + global_ui_context.keyboard->selection_length = 1; + state->selection_start_index = global_ui_context.keyboard->selection_begin_offset; +#endif + } + + if (state->state) + { + s32 len = utf8len(global_ui_context.keyboard->input_text); + s32 old_len = utf8len(state->buffer); + + // check if text changes, add to history if true + bool is_lctrl_down = global_ui_context.keyboard->keys[KEY_LEFT_CONTROL]; + + // go to previous state + if (is_lctrl_down && keyboard_is_key_pressed(global_ui_context.keyboard, KEY_Z) && state->history.length) + { + textbox_history_entry history_entry; + history_entry.text = mem_alloc(strlen(state->buffer)+1); + history_entry.cursor_offset = last_cursor_pos; + string_copyn(history_entry.text, state->buffer, MAX_INPUT_LENGTH); + array_push(&state->future, &history_entry); + + global_ui_context.keyboard->text_changed = true; + + textbox_history_entry *old_text = array_at(&state->history, state->history.length-1); + string_copyn(state->buffer, old_text->text, MAX_INPUT_LENGTH); + keyboard_set_input_text(global_ui_context.keyboard, state->buffer); + + mem_free(old_text->text); + array_remove_at(&state->history, state->history.length-1); + + global_ui_context.keyboard->cursor = old_text->cursor_offset; + } + else if (is_lctrl_down && + keyboard_is_key_pressed(global_ui_context.keyboard, KEY_Y) && state->future.length) + { + textbox_history_entry history_entry; + history_entry.text = mem_alloc(strlen(state->buffer)+1); + history_entry.cursor_offset = last_cursor_pos; + string_copyn(history_entry.text, state->buffer, MAX_INPUT_LENGTH); + array_push(&state->history, &history_entry); + + global_ui_context.keyboard->text_changed = true; + + textbox_history_entry *old_text = array_at(&state->future, state->future.length-1); + string_copyn(state->buffer, old_text->text, MAX_INPUT_LENGTH); + keyboard_set_input_text(global_ui_context.keyboard, state->buffer); + + mem_free(old_text->text); + array_remove_at(&state->future, state->future.length-1); + + global_ui_context.keyboard->cursor = old_text->cursor_offset; + } + else + { + if (global_ui_context.keyboard->text_changed) + { + if (last_cursor_pos != -1) + { + textbox_history_entry history_entry; + history_entry.text = mem_alloc(strlen(state->buffer)+1); + history_entry.cursor_offset = last_cursor_pos; + string_copyn(history_entry.text, state->buffer, MAX_INPUT_LENGTH); + array_push(&state->history, &history_entry); + } + } + + string_copyn(state->buffer, global_ui_context.keyboard->input_text, state->max_len); + if (global_ui_context.keyboard->cursor > state->max_len) + { + global_ui_context.keyboard->cursor = state->max_len; + utf8_str_replace_at(global_ui_context.keyboard->input_text,global_ui_context.keyboard->cursor, 0); + } + } + + // cursor ticking after text change + if (last_cursor_pos != global_ui_context.keyboard->cursor || global_ui_context.keyboard->text_changed) + cursor_tick = 0; + + // draw cursor + cursor_text_w = calculate_text_width_upto(global_ui_context.font_small, + state->buffer, global_ui_context.keyboard->cursor); + + s32 text_w = calculate_text_width(global_ui_context.font_small, state->buffer); + + cursor_x = text_x + cursor_text_w - state->diff; + + // change offset after cursor position change + if (!is_selecting && !global_ui_context.keyboard->has_selection) + { + if (cursor_text_w < state->diff) + { + state->diff = cursor_text_w; + } + if (cursor_text_w - state->diff > TEXTBOX_WIDTH - 10) + { + state->diff = (cursor_text_w) - (TEXTBOX_WIDTH - 10); + } + } + + // make sure offset is recalculated when text changes or a portion of text is changed so the textbox doesnt end up half empty +#if 1 + if (!clicked_to_select && !clicked_to_set_cursor && !is_selecting && !global_ui_context.keyboard->has_selection && global_ui_context.keyboard->text_changed) + { + if ((text_w > TEXTBOX_WIDTH -10) && (global_ui_context.keyboard->text_changed || global_ui_context.keyboard->cursor != last_cursor_pos)) + { + state->diff = text_w - TEXTBOX_WIDTH + 10; + } + else if ((text_w <= TEXTBOX_WIDTH -10)) + { + state->diff = 0; + } + } +#endif + } + + s32 curr_index = calculate_cursor_position(global_ui_context.font_small, + state->buffer, mouse_x + state->diff - text_x); + + ////////////////////////////////// + { + if (curr_index != state->last_click_cursor_index && state->attempting_to_select) + { + clicked_to_select = true; + state->attempting_to_select = false; + } + } + + + // select first character on click + if (clicked_to_select) + { +#if 1 + global_ui_context.keyboard->has_selection = true; + //global_ui_context.keyboard->selection_begin_offset = calculate_cursor_position(global_ui_context.font_small, + //state->buffer, mouse_x + state->diff - text_x); + global_ui_context.keyboard->selection_length = 0; + state->selection_start_index = global_ui_context.keyboard->selection_begin_offset; + + state->selection_start_index--; +#endif + } + + + if (is_selecting) + { + // move text offset x when selecting so we can select more text than available on screen. + if (global_ui_context.mouse->x < x + 10) + { + s32 text_w = calculate_text_width(global_ui_context.font_small, state->buffer); + if (text_w > TEXTBOX_WIDTH-10) + { + state->diff -= TEXTBOX_SCROLL_X_SPEED; + if (state->diff < 0) state->diff = 0; + } + } + if (global_ui_context.mouse->x > x + TEXTBOX_WIDTH - 10) + { + s32 text_w = calculate_text_width(global_ui_context.font_small, state->buffer); + s32 diff = text_w - TEXTBOX_WIDTH + 10; + + if (text_w > TEXTBOX_WIDTH-10) + { + state->diff += TEXTBOX_SCROLL_X_SPEED; + if (state->diff > diff) + state->diff = diff; + } + } + /////////////////////////////////////////////////////////// + } + + // change selection area based on cursor position. + // if double clicked to select the entire textbox we should only + // do this when the mouse has moved enough to select a new character +#if 1 + if (is_selecting) + { + s32 index = calculate_cursor_position(global_ui_context.font_small, + state->buffer, mouse_x + state->diff - text_x)+1; + + if (double_clicked_to_select_first) + state->double_clicked_to_select_cursor_index = index; + + if (!state->double_clicked_to_select || (state->double_clicked_to_select && index != state->double_clicked_to_select_cursor_index)) + { + if (index <= state->selection_start_index+1) + { + global_ui_context.keyboard->selection_begin_offset = index - 1; + global_ui_context.keyboard->selection_length = state->selection_start_index - index + 2; + } + else if (index > state->selection_start_index) + { + global_ui_context.keyboard->selection_begin_offset = state->selection_start_index+1; + global_ui_context.keyboard->selection_length = index - state->selection_start_index-2; + } + + state->double_clicked_to_select = false; + } + } + + // render cursor + if (state->state) + { + last_cursor_pos = global_ui_context.keyboard->cursor; + + s32 cursor_y = y + 4; + s32 cursor_h = TEXTBOX_HEIGHT - 8; + s32 cursor_w = 2; + + if (cursor_tick % 40 < 20 && !global_ui_context.keyboard->has_selection) + render_rectangle(cursor_x, cursor_y, cursor_w, cursor_h, global_ui_context.style.textbox_foreground); + } + + + // render selection area + if (global_ui_context.keyboard->has_selection && state->state) + { + char tmp_buffer[MAX_INPUT_LENGTH]; + utf8_str_copy_upto(state->buffer, + global_ui_context.keyboard->selection_begin_offset, + tmp_buffer); + + s32 selection_start_x = calculate_text_width(global_ui_context.font_small, + tmp_buffer); + + utf8_str_copy_upto( + utf8_str_upto( + state->buffer, + global_ui_context.keyboard->selection_begin_offset), + global_ui_context.keyboard->selection_length, + tmp_buffer); + + s32 selection_width = calculate_text_width(global_ui_context.font_small, + tmp_buffer); + + render_rectangle(text_x - state->diff + selection_start_x, y+4, selection_width, TEXTBOX_HEIGHT-8, global_ui_context.style.textbox_active_border); + } +#endif + + if (!has_text) + { + render_text(global_ui_context.font_small, text_x - state->diff, text_y, + placeholder, global_ui_context.style.textbox_placeholder_foreground); + } + else + { + render_text(global_ui_context.font_small, text_x - state->diff, text_y, + state->buffer, global_ui_context.style.foreground); + } + + ui_pop_scissor(); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += TEXTBOX_WIDTH + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += TEXTBOX_HEIGHT + WIDGET_PADDING; + + return result || state->state; +} + +bool ui_push_hypertext_link(char *text) +{ + bool result = false; + + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + WIDGET_PADDING; + s32 text_h = global_ui_context.font_small->px_h; + s32 text_y = y + (BLOCK_HEIGHT/2) - (global_ui_context.font_small->px_h/2) + spacing_y; + s32 total_w = calculate_text_width(global_ui_context.font_small, text) + + WIDGET_PADDING + WIDGET_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + if (global_ui_context.layout.block_height < global_ui_context.font_small->px_h) + global_ui_context.layout.block_height = global_ui_context.font_small->px_h; + + color bg_color = global_ui_context.style.hypertext_foreground; + if (mouse_x >= text_x && mouse_x < text_x + total_w && mouse_y >= text_y && mouse_y < text_y+text_h && !global_ui_context.item_hovered) + { + if (is_left_clicked(global_ui_context.mouse)) + { + result = true; + } + bg_color = global_ui_context.style.hypertext_hover_foreground; + } + + s32 text_width = render_text(global_ui_context.font_small, text_x, text_y, text, bg_color); + + if (result) + render_rectangle(text_x, text_y + text_h-1, text_width, 1, bg_color); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; + + return result; +} + +void ui_push_textf(font *f, char *text) +{ + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + WIDGET_PADDING; + s32 text_y = y + (BLOCK_HEIGHT/2) - (f->px_h/2) + spacing_y; + s32 total_w = calculate_text_width(f, text) + + WIDGET_PADDING + WIDGET_PADDING; + + if (global_ui_context.layout.block_height < f->px_h) + global_ui_context.layout.block_height = f->px_h+5; + + render_text(f, text_x, text_y, text, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; +} + + +void ui_push_textf_width(font *f, char *text, s32 maxw) +{ + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + WIDGET_PADDING; + s32 text_y = y + (BLOCK_HEIGHT/2) - (f->px_h/2) + spacing_y; + s32 total_w = maxw + + WIDGET_PADDING + WIDGET_PADDING; + + if (global_ui_context.layout.block_height < f->px_h) + global_ui_context.layout.block_height = f->px_h+5; + + render_text_ellipsed(f, text_x, text_y, maxw, text, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; +} + + +void ui_push_text(char *text) +{ + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + WIDGET_PADDING; + s32 text_y = y + (BLOCK_HEIGHT/2) - (global_ui_context.font_small->px_h/2) + spacing_y; + s32 total_w = calculate_text_width(global_ui_context.font_small, text) + + WIDGET_PADDING + WIDGET_PADDING; + + if (global_ui_context.layout.block_height < global_ui_context.font_small->px_h) + global_ui_context.layout.block_height = global_ui_context.font_small->px_h+5; + + render_text(global_ui_context.font_small, text_x, text_y, text, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; +} + +void ui_push_text_width(char *text, s32 maxw) +{ + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + WIDGET_PADDING; + s32 text_y = y + (BLOCK_HEIGHT/2) - (global_ui_context.font_small->px_h/2) + spacing_y; + s32 total_w = maxw + + WIDGET_PADDING + WIDGET_PADDING; + + if (global_ui_context.layout.block_height < global_ui_context.font_small->px_h) + global_ui_context.layout.block_height = global_ui_context.font_small->px_h+5; + + render_text_ellipsed(global_ui_context.font_small, text_x, text_y, maxw, text, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; +} + +bool ui_push_checkbox(checkbox_state *state, char *title) +{ + bool result = false; + + s32 spacing_y = (BLOCK_HEIGHT - CHECKBOX_SIZE)/2; + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll() - spacing_y; + s32 text_x = x + CHECKBOX_SIZE + WIDGET_PADDING; + s32 text_y = y + (BLOCK_HEIGHT/2) - (global_ui_context.font_small->px_h/2) + spacing_y; + s32 total_w = calculate_text_width(global_ui_context.font_small, title) + + CHECKBOX_SIZE + WIDGET_PADDING + WIDGET_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + if (global_ui_context.layout.block_height < CHECKBOX_SIZE) + global_ui_context.layout.block_height = CHECKBOX_SIZE; + + render_rectangle_outline(x, y, CHECKBOX_SIZE, CHECKBOX_SIZE, 1, global_ui_context.style.border); + + s32 virt_top = y; + s32 virt_bottom = y + CHECKBOX_SIZE; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + if (mouse_x >= x && mouse_x < x + CHECKBOX_SIZE && mouse_y >= virt_top && mouse_y < virt_bottom && !global_ui_context.item_hovered) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + if (is_left_clicked(global_ui_context.mouse)) + { + state->state = !state->state; + result = true; + } + } + + if (state->state) + { + s32 spacing = 2; + render_rectangle(x+spacing, y+spacing, CHECKBOX_SIZE-(spacing*2), CHECKBOX_SIZE-(spacing*2), global_ui_context.style.border); + } + + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w; + else + global_ui_context.layout.offset_y += CHECKBOX_SIZE + WIDGET_PADDING; + + return result; +} + +inline bool is_shortcut_down(s32 shortcut_keys[2]) +{ + return keyboard_is_key_down(global_ui_context.keyboard, shortcut_keys[0]) && + keyboard_is_key_pressed(global_ui_context.keyboard, shortcut_keys[1]); +} + +bool ui_push_menu_item(char *title, char *shortcut) +{ + bool result = false; + + set_render_depth(30); + + global_ui_context.menu_item_count++; + + s32 x = global_ui_context.layout.prev_offset_x + global_ui_context.camera->x; + s32 w = MENU_ITEM_WIDTH; + s32 text_h = global_ui_context.font_small->px_h; + s32 h = MENU_BAR_HEIGHT; + s32 y = global_ui_context.layout.offset_y + (global_ui_context.menu_item_count * h)+1 + + global_ui_context.layout.menu_offset_y + global_ui_context.camera->y; + s32 text_y = y - (text_h / 2) + (h / 2); + s32 text_x = x + MENU_HORIZONTAL_PADDING; + s32 text_2_x = x + w - MENU_HORIZONTAL_PADDING + - calculate_text_width(global_ui_context.font_small, shortcut); + + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + color bg_color = global_ui_context.style.menu_background; + + if ((mouse_x >= x && mouse_x < x + w && mouse_y >= y && mouse_y < y + h)) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + bg_color = global_ui_context.style.menu_hover_background; + global_ui_context.item_hovered = true; + + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + result = true; + } + } + + render_rectangle(x, y, w, h, bg_color); + render_rectangle(x, y+MENU_BAR_HEIGHT, w, 1, global_ui_context.style.border); + + // borders + render_rectangle(x, y, w, 1, bg_color); + render_rectangle(x, y, 1, MENU_BAR_HEIGHT, global_ui_context.style.border); + render_rectangle(x+w, y, 1, MENU_BAR_HEIGHT+1, global_ui_context.style.border); + + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + render_text(global_ui_context.font_small, text_2_x, text_y, shortcut, global_ui_context.style.foreground); + + set_render_depth(1); + + return result; +} + +bool ui_push_image(image *img, s32 w, s32 h, s32 outline, color tint) +{ + bool result = false; + + if (!img->loaded) return result; + + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 total_w = w; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + s32 virt_top = y; + s32 virt_bottom = y + h; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y >= virt_top && mouse_y < virt_bottom && !global_ui_context.item_hovered) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + } + if (is_left_released(global_ui_context.mouse)) + { + result = true; + } + } + + render_image_tint(img,x,y,w,h,global_ui_context.style.image_outline_tint); + render_image_tint(img,x+outline,y+outline,w-(outline*2),h-(outline*2),tint); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; + + return result; +} + +bool ui_push_button(button_state *state, char *title) +{ + bool result = false; + + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 text_x = x + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 text_y = y + (BUTTON_HEIGHT/2) - (global_ui_context.font_small->px_h/2); + s32 total_w = calculate_text_width(global_ui_context.font_small, title) + + BUTTON_HORIZONTAL_TEXT_PADDING + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + s32 h = BUTTON_HEIGHT; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + color bg_color = global_ui_context.style.widget_background; + + s32 virt_top = y; + s32 virt_bottom = y + h; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y >= virt_top && mouse_y < virt_bottom && !global_ui_context.item_hovered) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + bg_color = global_ui_context.style.widget_hover_background; + + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + state->state = 1; + } + if (is_left_released(global_ui_context.mouse) && state->state) + { + state->state = 0; + result = true; + } + } + if (is_left_released(global_ui_context.mouse)) + { + //state->state = 0; + } + + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 1, global_ui_context.style.border); + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; + + return result; +} + + +bool ui_push_button_image(button_state *state, char *title, image *img) +{ + bool result = false; + + s32 x = global_ui_context.layout.offset_x + WIDGET_PADDING + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y + ui_get_scroll(); + s32 text_x = x + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 text_y = y + (BUTTON_HEIGHT/2) - (global_ui_context.font_small->px_h/2); + s32 text_w = calculate_text_width(global_ui_context.font_small, title); + s32 total_w = text_w + BUTTON_HORIZONTAL_TEXT_PADDING; + s32 mouse_x = global_ui_context.mouse->x + global_ui_context.camera->x; + s32 mouse_y = global_ui_context.mouse->y + global_ui_context.camera->y; + s32 h = BUTTON_HEIGHT; + + if (title[0] == 0) + { + total_w = 0; + } + + color bg_color = global_ui_context.style.widget_background; + + if (global_ui_context.layout.block_height < h) + global_ui_context.layout.block_height = h; + + int icon_w = 1; + int icon_h = 1; + if (img->loaded) + { + float max_icon_size = BUTTON_HEIGHT - (BUTTON_IMAGE_PADDING*2); + float scale = 1.0f; + if (img->width >= img->height) + { + scale = img->width / max_icon_size; + + icon_w = img->width / scale; + icon_h = icon_w; + } + else if (img->height >= img->width) + { + scale = img->height / max_icon_size; + + icon_h = img->height / scale; + icon_w = icon_h; + } + + total_w += icon_w + (BUTTON_IMAGE_SPACING*2); + } + + s32 virt_top = y; + s32 virt_bottom = y + h; + if (global_ui_context.layout.scroll->in_scroll) + { + s32 bottom = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + if (bottom < virt_bottom) + virt_bottom = bottom; + s32 top = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + if (top > virt_top) + virt_top = top; + } + + if (mouse_x >= x && mouse_x < x + total_w && mouse_y >= virt_top && mouse_y < virt_bottom && !global_ui_context.item_hovered) + { + platform_set_cursor(global_ui_context.layout.active_window, CURSOR_POINTER); + bg_color = global_ui_context.style.widget_hover_background; + + if (is_left_clicked(global_ui_context.mouse)) + { + global_ui_context.mouse->left_state &= ~MOUSE_CLICK; + state->state = 1; + } + if (is_left_released(global_ui_context.mouse) && state->state) + { + state->state = 0; + result = true; + } + } + if (is_left_released(global_ui_context.mouse)) + { + state->state = 0; + } + + render_rectangle(x, y, total_w, BUTTON_HEIGHT, bg_color); + render_rectangle_outline(x, y, total_w, BUTTON_HEIGHT, 1, global_ui_context.style.border); + render_text(global_ui_context.font_small, text_x, text_y, title, global_ui_context.style.foreground); + render_image(img, x + total_w - icon_w - BUTTON_IMAGE_SPACING, y + BUTTON_IMAGE_PADDING, img->width, img->height); + + if (global_ui_context.layout.layout_direction == LAYOUT_HORIZONTAL) + global_ui_context.layout.offset_x += total_w + WIDGET_PADDING; + else + global_ui_context.layout.offset_y += BUTTON_HEIGHT + WIDGET_PADDING; + + return result; +} + +inline void ui_end_menu_bar() +{ + global_ui_context.layout.layout_direction = LAYOUT_VERTICAL; + global_ui_context.layout.offset_x = 0; + global_ui_context.layout.offset_y += MENU_BAR_HEIGHT; + + ui_push_separator(); +} + +inline void ui_destroy() +{ + array_destroy(&global_ui_context.active_menus); +} + +void ui_scroll_begin(scroll_state *state) +{ + global_ui_context.layout.scroll = state; + global_ui_context.layout.scroll->in_scroll = true; + //global_ui_context.layout.scroll->height = height; + + s32 w = global_ui_context.layout.width; + s32 h = state->height; + s32 x = global_ui_context.layout.offset_x + global_ui_context.camera->x; + s32 y = global_ui_context.layout.offset_y + global_ui_context.camera->y - WIDGET_PADDING; + + //global_ui_context.layout.offset_x += WIDGET_PADDING; + global_ui_context.layout.start_offset_x = global_ui_context.layout.offset_x; + //global_ui_context.layout.offset_y += WIDGET_PADDING; + global_ui_context.layout.scroll->scroll_start_offset_y = global_ui_context.layout.offset_y; + + //render_rectangle_outline(x, y, w, h, 1, global_ui_context.style.border); + render_set_scissor(global_ui_context.layout.active_window, x, y, w, h); +} + +void ui_scroll_end() +{ + s32 max_scroll = (global_ui_context.layout.scroll->scroll_start_offset_y - + global_ui_context.layout.offset_y) + global_ui_context.layout.scroll->height; + + //global_ui_context.layout.offset_x -= WIDGET_PADDING; + global_ui_context.layout.offset_y = global_ui_context.layout.scroll->scroll_start_offset_y + global_ui_context.layout.scroll->height; + global_ui_context.layout.offset_y += WIDGET_PADDING; + + // draw scrollbar + if (max_scroll < 0) + { + s32 scroll_y = 0; + if (global_ui_context.mouse->scroll_state == SCROLL_UP) + scroll_y+=SCROLL_SPEED; + if (global_ui_context.mouse->scroll_state == SCROLL_DOWN) + scroll_y-=SCROLL_SPEED; + global_ui_context.layout.scroll->scroll += scroll_y; + if (global_ui_context.layout.scroll->scroll > 0) + global_ui_context.layout.scroll->scroll = 0; + if (global_ui_context.layout.scroll->scroll < max_scroll) + global_ui_context.layout.scroll->scroll = max_scroll; + + float percentage = global_ui_context.layout.scroll->scroll / + (float)max_scroll; + + float scrollbar_height_percentage = -(max_scroll - global_ui_context.layout.scroll->height) / (float)global_ui_context.layout.scroll->height; + + s32 scrollbar_height = global_ui_context.layout.scroll->height / scrollbar_height_percentage; + s32 scrollbar_pos_y = global_ui_context.layout.scroll->scroll_start_offset_y - WIDGET_PADDING; + + s32 tw = global_ui_context.layout.width - WIDGET_PADDING*2; + s32 tx = global_ui_context.layout.offset_x + global_ui_context.camera->x + WIDGET_PADDING; + + s32 scrollbar_pos_x = tx + tw + WIDGET_PADDING - 10; + + scrollbar_pos_y += (global_ui_context.layout.scroll->height - + scrollbar_height + WIDGET_PADDING - 2) * percentage; + + render_reset_scissor(); + render_rectangle(scrollbar_pos_x, scrollbar_pos_y, 10, scrollbar_height, global_ui_context.style.scrollbar_handle_background); + } + render_reset_scissor(); + global_ui_context.layout.scroll->in_scroll = false; +} diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..b9a1f0d --- /dev/null +++ b/src/ui.h @@ -0,0 +1,205 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#ifndef INCLUDE_UI +#define INCLUDE_UI + +#define BLOCK_HEIGHT 25 +#define MENU_BAR_HEIGHT 25 +#define MENU_HORIZONTAL_PADDING 10 +#define WIDGET_PADDING 8 +#define BUTTON_HORIZONTAL_TEXT_PADDING 15 +#define MENU_ITEM_WIDTH 220 +#define CHECKBOX_SIZE BLOCK_HEIGHT - 8 +#define TEXTBOX_WIDTH 280 +#define TEXTBOX_HEIGHT BLOCK_HEIGHT +#define BUTTON_HEIGHT BLOCK_HEIGHT +#define BUTTON_IMAGE_PADDING 5 +#define BUTTON_IMAGE_SPACING 8 +#define DROPDOWN_WIDTH 225 +#define DROPDOWN_ITEM_WIDTH 225 +#define TEXTBOX_SCROLL_X_SPEED 32 + +typedef enum t_ui_style_type +{ + UI_STYLE_LIGHT = 1, + UI_STYLE_DARK = 2, +} ui_style_type; + +typedef struct t_ui_style +{ + u16 id; + color foreground; + color background; + color border; + color textbox_background; + color textbox_active_border; + color textbox_foreground; + color image_outline_tint; + color scrollbar_handle_background; + color info_bar_background; + color error_foreground; + color item_hover_background; + color scrollbar_background; + color menu_background; + color menu_hover_background; + color menu_foreground; + color widget_hover_background; + color widget_background; + color hypertext_foreground; + color hypertext_hover_foreground; + color textbox_placeholder_foreground; +} ui_style; + +typedef enum t_layout_direction +{ + LAYOUT_HORIZONTAL, + LAYOUT_VERTICAL, +} layout_direction; + +typedef struct t_dropdown_state +{ + bool state; + int selected_index; +} dropdown_state; + +typedef struct t_scroll_state +{ + s32 height; + s32 scroll; + s32 scroll_start_offset_y; + bool in_scroll; +} scroll_state; + +typedef struct t_ui_layout +{ + s32 dropdown_item_count; + s32 dropdown_x; + s32 offset_x; + s32 offset_y; + platform_window *active_window; + layout_direction layout_direction; + s32 prev_offset_x; + s32 width; + s32 height; + s32 menu_offset_y; + s32 block_height; + s32 start_offset_y; + s32 start_offset_x; + scroll_state *scroll; + s32 padding; + dropdown_state *active_dropdown_state; +} ui_layout; + +typedef struct t_textbox_history_entry +{ + char *text; + s32 cursor_offset; +} textbox_history_entry; + +typedef struct t_textbox_state +{ + bool deselect_on_enter; + char *buffer; + s32 selection_start_index; + bool state; + s32 diff; + bool double_clicked_to_select; + s32 double_clicked_to_select_cursor_index; + s32 max_len; + s32 text_offset_x; + bool attempting_to_select; + array history; + array future; + s32 last_click_cursor_index; +} textbox_state; + +typedef struct t_checkbox_state +{ + bool state; +} checkbox_state; + +typedef struct t_button_state +{ + bool state; +} button_state; + +typedef struct t_ui_context +{ + ui_style style; + ui_layout layout; + keyboard_input *keyboard; + mouse_input *mouse; + camera *camera; + font *font_small; + array active_menus; + u32 next_id; + s32 menu_item_count; + dropdown_state *active_dropdown; + textbox_state *current_active_textbox; + bool item_hovered; +} ui_context; + +///// our global ui states //// +checkbox_state checkbox_recursive; +textbox_state textbox_search_text; +textbox_state textbox_path; +textbox_state textbox_file_filter; +button_state button_select_directory; +button_state button_find_text; +button_state button_cancel; +/////////////////////////////// + +ui_context global_ui_context; + +u32 ui_get_id(); +void ui_create(platform_window *window, keyboard_input *keyboard, mouse_input *mouse, camera *camera, font *font_small); +void ui_set_active_window(platform_window *window); +void ui_destroy(); +void ui_begin(); +void ui_end(); +bool ui_is_menu_active(u32 id); +char* name_of_day(s32 day); +char* name_of_month(s32 month); +void ui_set_style(u16 style); +void set_active_textbox(textbox_state *textbox); + +// widget initialization +checkbox_state ui_create_checkbox(bool selected); +textbox_state ui_create_textbox(u16 max_len); +button_state ui_create_button(); +scroll_state ui_create_scroll(s32 scroll); +dropdown_state ui_create_dropdown(); + +void ui_destroy_textbox(textbox_state *state); + +// widgets +bool is_shortcut_down(s32 shortcut_keys[2]); +void ui_begin_menu_bar(); +bool ui_push_menu(char *title); +bool ui_push_menu_item(char *title, char *shortcut); +void ui_push_menu_item_separator(); +bool ui_push_dropdown(dropdown_state *state, char *title); +bool ui_push_dropdown_item(image *icon, char *title, s32 index); +void ui_push_separator(); +void ui_push_vertical_dragbar(); +void ui_block_begin(layout_direction direction); +void ui_block_end(); +void ui_end_menu_bar(); +void ui_push_text(char *text); +void ui_push_textf(font *f, char *text); +void ui_push_textf_width(font *f, char *text, s32 maxw); +bool ui_push_hypertext_link(char *text); +bool ui_push_color_button(char *text, bool selected, color color); +bool ui_push_image(image *img, s32 w, s32 h, s32 outline, color tint); +bool ui_push_checkbox(checkbox_state *state, char *title); +bool ui_push_textbox(textbox_state *state, char *title); +bool ui_push_button(button_state *button, char *title); +bool ui_push_button_image(button_state *button, char *title, image *img); +void ui_scroll_begin(scroll_state *state); +void ui_scroll_end(); + +#endif
\ No newline at end of file diff --git a/src/windows/platform.c b/src/windows/platform.c new file mode 100644 index 0000000..2187806 --- /dev/null +++ b/src/windows/platform.c @@ -0,0 +1,1267 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#include <locale.h> +#include <windows.h> +#include <GL/gl.h> +#include <stdbool.h> +#include <sysinfoapi.h> +#include <wingdi.h> +#include <errno.h> +#include <shlwapi.h> +#include <objbase.h> +#include <shellapi.h> +#include <gdiplus.h> +#include <shlobj.h> +#include "../external/LooplessSizeMove.c" + +struct t_platform_window +{ + HWND window_handle; + HDC hdc; + HGLRC gl_context; + WNDCLASS window_class; + + s32 min_width; + s32 min_height; + s32 max_width; + s32 max_height; + + // shared window properties + s32 width; + s32 height; + bool is_open; + bool has_focus; + cursor_type curr_cursor_type; + cursor_type next_cursor_type; +}; + +extern BOOL GetPhysicallyInstalledSystemMemory(PULONGLONG TotalMemoryInKilobytes); + +LARGE_INTEGER perf_frequency; +static HINSTANCE instance; +platform_window *current_window_to_handle; +keyboard_input *current_keyboard_to_handle; +mouse_input *current_mouse_to_handle; + +int cmd_show; + +bool platform_get_clipboard(platform_window *window, char *buffer) +{ + if (!OpenClipboard(NULL)) + return false; + + if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) + { + CloseClipboard(); + return false; + } + + wchar_t* clip_str = GetClipboardData(CF_UNICODETEXT); + if (!clip_str) + { + CloseClipboard(); + return false; + } + + WideCharToMultiByte(CP_UTF8, 0, clip_str, -1, buffer, MAX_INPUT_LENGTH ,0,0); + + CloseClipboard(); + return true; +} + +bool platform_set_clipboard(platform_window *window, char *buffer) +{ + HANDLE clipboard_data; + + int char_num = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, 0, 0); + wchar_t *convstr = mem_alloc(char_num*2); + int result = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, convstr, char_num); + + size_t len = result; + size_t size = (len+1) * sizeof(wchar_t); + LPSTR dst; + + if (!OpenClipboard(NULL)) + return false; + + clipboard_data = GlobalAlloc(GMEM_MOVEABLE, size); + if (clipboard_data) + { + dst = GlobalLock(clipboard_data); + memmove(dst, convstr, size); + dst[len*2] = 0; + GlobalUnlock(clipboard_data); + + SetClipboardData(CF_UNICODETEXT, clipboard_data); + } + else + { + CloseClipboard(); + return false; + } + + CloseClipboard(); + return true; +} + +inline void platform_show_alert(char *title, char *message) +{ + // not implemented +} + +inline void platform_destroy() +{ + assets_destroy(); + +#if defined(MODE_DEVELOPER) + memory_print_leaks(); +#endif +} + +bool is_platform_in_darkmode() +{ + char *key = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\\"; + + HKEY result; + LSTATUS o = RegOpenKeyExA(HKEY_CURRENT_USER, key, 0, KEY_READ, &result); + + if (o == 0) + { + BYTE data; + DWORD len = 4; + RegQueryValueExA(result, "AppsUseLightTheme", NULL, NULL, &data, &len); + + if (data == 1) return true; + } + + return false; +} + +inline void platform_set_cursor(platform_window *window, cursor_type type) +{ + if (window->next_cursor_type != type) + { + window->next_cursor_type = type; + } +} + +bool platform_directory_exists(char *path) +{ + return PathFileExistsA(path) == TRUE; +} + +static void create_key_tables() +{ + keycode_map[0x30] = KEY_0; + keycode_map[0x31] = KEY_1; + keycode_map[0x32] = KEY_2; + keycode_map[0x33] = KEY_3; + keycode_map[0x34] = KEY_4; + keycode_map[0x35] = KEY_5; + keycode_map[0x36] = KEY_6; + keycode_map[0x37] = KEY_7; + keycode_map[0x38] = KEY_8; + keycode_map[0x39] = KEY_9; + keycode_map[0x41] = KEY_A; + keycode_map[0x42] = KEY_B; + keycode_map[0x43] = KEY_C; + keycode_map[0x44] = KEY_D; + keycode_map[0x45] = KEY_E; + keycode_map[0x46] = KEY_F; + keycode_map[0x47] = KEY_G; + keycode_map[0x48] = KEY_H; + keycode_map[0x49] = KEY_I; + keycode_map[0x4A] = KEY_J; + keycode_map[0x4B] = KEY_K; + keycode_map[0x4C] = KEY_L; + keycode_map[0x4D] = KEY_M; + keycode_map[0x4E] = KEY_N; + keycode_map[0x4F] = KEY_O; + keycode_map[0x50] = KEY_P; + keycode_map[0x51] = KEY_Q; + keycode_map[0x52] = KEY_R; + keycode_map[0x53] = KEY_S; + keycode_map[0x54] = KEY_T; + keycode_map[0x55] = KEY_U; + keycode_map[0x56] = KEY_V; + keycode_map[0x57] = KEY_W; + keycode_map[0x58] = KEY_X; + keycode_map[0x59] = KEY_Y; + keycode_map[0x5A] = KEY_Z; + + keycode_map[VK_OEM_7] = KEY_APOSTROPHE; + keycode_map[VK_OEM_102] = KEY_BACKSLASH; + keycode_map[VK_OEM_COMMA] = KEY_COMMA; + keycode_map[VK_OEM_3] = KEY_GRAVE_ACCENT; + keycode_map[VK_OEM_4] = KEY_LEFT_BRACKET; + keycode_map[VK_OEM_MINUS] = KEY_MINUS; + keycode_map[VK_OEM_PERIOD] = KEY_PERIOD; + + keycode_map[VK_BACK] = KEY_BACKSPACE; + keycode_map[VK_DELETE] = KEY_DELETE; + keycode_map[VK_END] = KEY_END; + keycode_map[VK_RETURN] = KEY_ENTER; + keycode_map[VK_ESCAPE] = KEY_ESCAPE; + keycode_map[VK_HOME] = KEY_HOME; + keycode_map[VK_INSERT] = KEY_INSERT; + keycode_map[VK_MENU] = KEY_MENU; + keycode_map[VK_NEXT] = KEY_PAGE_DOWN; + keycode_map[VK_PRIOR] = KEY_PAGE_UP; + keycode_map[VK_PAUSE] = KEY_PAUSE; + keycode_map[VK_TAB] = KEY_TAB; + keycode_map[VK_CAPITAL] = KEY_CAPS_LOCK; + keycode_map[VK_NUMLOCK] = KEY_NUM_LOCK; + keycode_map[VK_SCROLL] = KEY_SCROLL_LOCK; + keycode_map[0x70] = KEY_F1; + keycode_map[0x71] = KEY_F2; + keycode_map[0x72] = KEY_F3; + keycode_map[0x73] = KEY_F4; + keycode_map[0x74] = KEY_F5; + keycode_map[0x75] = KEY_F6; + keycode_map[0x76] = KEY_F7; + keycode_map[0x77] = KEY_F8; + keycode_map[0x78] = KEY_F9; + keycode_map[0x79] = KEY_F10; + keycode_map[0x7A] = KEY_F11; + keycode_map[0x7B] = KEY_F12; + keycode_map[0x7C] = KEY_F13; + keycode_map[0x7D] = KEY_F14; + keycode_map[0x7E] = KEY_F15; + keycode_map[0x7F] = KEY_F16; + keycode_map[0x80] = KEY_F17; + keycode_map[0x81] = KEY_F18; + keycode_map[0x82] = KEY_F19; + keycode_map[0x83] = KEY_F20; + keycode_map[0x84] = KEY_F21; + keycode_map[0x85] = KEY_F22; + keycode_map[0x86] = KEY_F23; + keycode_map[0x87] = KEY_F24; + keycode_map[0x88] = KEY_LEFT_ALT; + keycode_map[VK_CONTROL] = KEY_LEFT_CONTROL; + keycode_map[VK_LCONTROL] = KEY_LEFT_CONTROL; + keycode_map[VK_LSHIFT] = KEY_LEFT_SHIFT; + keycode_map[VK_LWIN] = KEY_LEFT_SUPER; + keycode_map[VK_SNAPSHOT] = KEY_PRINT_SCREEN; + keycode_map[VK_RMENU] = KEY_RIGHT_ALT; + keycode_map[VK_RCONTROL] = KEY_RIGHT_CONTROL; + keycode_map[VK_RSHIFT] = KEY_RIGHT_SHIFT; + keycode_map[VK_RWIN] = KEY_RIGHT_SUPER; + keycode_map[VK_DOWN] = KEY_DOWN; + keycode_map[VK_LEFT] = KEY_LEFT; + keycode_map[VK_RIGHT] = KEY_RIGHT; + keycode_map[VK_UP] = KEY_UP; + + keycode_map[VK_NUMPAD0] = KEY_KP_0; + keycode_map[VK_NUMPAD1] = KEY_KP_1; + keycode_map[VK_NUMPAD2] = KEY_KP_2; + keycode_map[VK_NUMPAD3] = KEY_KP_3; + keycode_map[VK_NUMPAD4] = KEY_KP_4; + keycode_map[VK_NUMPAD5] = KEY_KP_5; + keycode_map[VK_NUMPAD6] = KEY_KP_6; + keycode_map[VK_NUMPAD7] = KEY_KP_7; + keycode_map[VK_NUMPAD8] = KEY_KP_8; + keycode_map[VK_NUMPAD9] = KEY_KP_9; + keycode_map[VK_ADD] = KEY_KP_ADD; + keycode_map[VK_DECIMAL] = KEY_KP_DECIMAL; + keycode_map[VK_DIVIDE] = KEY_KP_DIVIDE; + keycode_map[VK_MULTIPLY] = KEY_KP_MULTIPLY; + keycode_map[VK_SUBTRACT] = KEY_KP_SUBTRACT; +} + +bool platform_file_exists(char *path) +{ + DWORD dwAttrib = GetFileAttributes(path); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +void platform_create_config_directory() +{ + char tmp[PATH_MAX]; + if(SUCCEEDED(SHGetFolderPathA(0, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, tmp))) + { + string_appendn(tmp, "/text-search", PATH_MAX); + } + + + if (!platform_directory_exists(tmp)) + { + CreateDirectoryA(tmp, NULL); + } +} + +char* get_config_save_location(char *buffer) +{ + if(SUCCEEDED(SHGetFolderPathA(0, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, buffer))) + { + string_appendn(buffer, "\\text-search\\config.txt", MAX_INPUT_LENGTH); + return buffer; + } + + return 0; +} + +void platform_show_message(platform_window *window, char *message, char *title) +{ + HWND handle = window ? window->window_handle : NULL; + MessageBox(handle, message, title, MB_ICONINFORMATION | MB_OK); +} + +LRESULT CALLBACK main_window_callback(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + LRESULT result = 0; + + if (message == WM_SIZE) + { + u32 width = lparam&0xFFFF; + u32 height = lparam>>16; + + current_window_to_handle->width = width; + current_window_to_handle->height = height; + } + else if (message == WM_CHAR) + { + if (current_keyboard_to_handle->take_input) + { + char buf[5]; + memset(buf, 0, 5); + char *ch = 0; + + wchar_t codep = wparam; + + WideCharToMultiByte(CP_UTF8, 0, &codep, 1, buf, 5 ,0,0); + + if (utf8len(buf) == 1) + { + char val = buf[0]; + + if (current_keyboard_to_handle->input_mode == INPUT_NUMERIC) + { + if (!(val >= 48 && val <= 57)) + { + ch = 0; + } + else + { + snprintf(buf, 2, "%c", val); + ch = buf; + } + } + else if (val >= 32 && val <= 126) + { + snprintf(buf, 5, "%c", val); + ch = buf; + } + } + + if (ch != 0) + keyboard_handle_input_string(current_window_to_handle, current_keyboard_to_handle, ch); + } + } + else if (message == WM_MOUSELEAVE) + { + //current_mouse_to_handle->x = MOUSE_OFFSCREEN; + //current_mouse_to_handle->y = MOUSE_OFFSCREEN; + } + else if (message == WM_KILLFOCUS) + { + current_mouse_to_handle->x = MOUSE_OFFSCREEN; + current_mouse_to_handle->y = MOUSE_OFFSCREEN; + + current_window_to_handle->has_focus = false; + memset(current_keyboard_to_handle->keys, 0, MAX_KEYCODE); + } + else if (message == WM_SETFOCUS) + { + current_window_to_handle->has_focus = true; + } + else if (message == WM_KEYDOWN) + { + s32 key = wparam; + + current_keyboard_to_handle->keys[keycode_map[key]] = true; + current_keyboard_to_handle->input_keys[keycode_map[key]] = true; + + if (current_keyboard_to_handle->take_input) + keyboard_handle_input_string(current_window_to_handle, + current_keyboard_to_handle, 0); + } + else if (message == WM_KEYUP) + { + s32 key = wparam; + current_keyboard_to_handle->keys[keycode_map[key]] = false; + current_keyboard_to_handle->input_keys[keycode_map[key]] = false; + } + else if (message == WM_LBUTTONDOWN || + message == WM_RBUTTONDOWN || + message == WM_MBUTTONDOWN || + message == WM_MOUSEWHEEL) + { + bool is_left_down = wparam & MK_LBUTTON; + bool is_right_down = wparam & MK_RBUTTON; + bool is_middle_down = wparam & MK_MBUTTON; + + u64 ev_time = platform_get_time(TIME_FULL, TIME_MILI_S); + static u64 last_ev_time = 0; + + if (message == WM_MOUSEWHEEL) + { + s16 scroll_val = wparam>>16; + + if (scroll_val < 0) + current_mouse_to_handle->scroll_state = SCROLL_DOWN; + else + current_mouse_to_handle->scroll_state = SCROLL_UP; + } + + if (is_left_down) + { + if (ev_time - last_ev_time < 200) + { + current_mouse_to_handle->left_state |= MOUSE_DOUBLE_CLICK; + } + + current_mouse_to_handle->left_state |= MOUSE_DOWN; + current_mouse_to_handle->left_state |= MOUSE_CLICK; + + current_mouse_to_handle->total_move_x = 0; + current_mouse_to_handle->total_move_y = 0; + last_ev_time = ev_time; + } + if (is_right_down) + { + current_mouse_to_handle->right_state |= MOUSE_DOWN; + current_mouse_to_handle->right_state |= MOUSE_CLICK; + } + } + else if (message == WM_LBUTTONUP || + message == WM_RBUTTONUP || + message == WM_MBUTTONUP) + { + bool is_left_up = message == WM_LBUTTONUP; + bool is_right_up = message == WM_RBUTTONUP; + bool is_middle_up = message == WM_MBUTTONUP; + + if (is_left_up) + { + current_mouse_to_handle->left_state = MOUSE_RELEASE; + } + if (is_right_up) + { + current_mouse_to_handle->right_state = MOUSE_RELEASE; + } + } + else if (message == WM_MOUSEMOVE) + { + current_window_to_handle->curr_cursor_type = -999; + +#if 0 + s32 x = lparam&0xFFFF; + s32 y = lparam>>16; + + current_mouse_to_handle->x = x; + current_mouse_to_handle->y = y; +#endif + + TRACKMOUSEEVENT track; + track.cbSize = sizeof(track); + track.dwFlags = TME_LEAVE; + track.hwndTrack = current_window_to_handle->window_handle; + TrackMouseEvent(&track); + } + else if (message == WM_GETMINMAXINFO) + { + MINMAXINFO *info = (MINMAXINFO*)lparam; + + info->ptMinTrackSize.x = current_window_to_handle->min_width; + info->ptMinTrackSize.y = current_window_to_handle->min_height; + + if (current_window_to_handle->max_width) + info->ptMaxTrackSize.x = current_window_to_handle->max_width; + if (current_window_to_handle->max_height) + info->ptMaxTrackSize.y = current_window_to_handle->max_height; + } + else if (message == WM_DESTROY) + { + current_window_to_handle->is_open = false; + } + else if (message == WM_CLOSE) + { + current_window_to_handle->is_open = false; + } + else + { + result = LSMProc(window, message, wparam, lparam); + } + + return result; +} + +void platform_window_set_title(platform_window *window, char *name) +{ + SetWindowText(window->window_handle, name); +} + +vec2 platform_get_window_size(platform_window *window) +{ + RECT rec; + GetWindowRect(window->window_handle, &rec); + vec2 res; + res.x = rec.right - rec.left; + res.y = rec.bottom - rec.top; + return res; +} + +void platform_get_focus(platform_window *window) +{ + SetFocus(window->window_handle); +} + +platform_window platform_open_window(char *name, u16 width, u16 height, u16 max_w, u16 max_h, u16 min_w, u16 min_h) +{ +#if !defined(MODE_GDBDEBUG) && !defined(MODE_DEVELOPER) + ShowWindow(GetConsoleWindow(), SW_HIDE); +#endif + + platform_window window; + window.has_focus = true; + window.window_handle = 0; + window.hdc = 0; + window.width = width; + window.height = height; + window.min_width = min_w; + window.min_height = min_h; + window.max_width = max_w; + window.max_height = max_h; + window.curr_cursor_type = -1; + window.next_cursor_type = CURSOR_DEFAULT; + + current_window_to_handle = &window; + + memset(&window.window_class, 0, sizeof(WNDCLASS)); + window.window_class.style = CS_OWNDC; + window.window_class.lpfnWndProc = main_window_callback; + window.window_class.hInstance = instance; + window.window_class.lpszClassName = name; + window.window_class.hIcon = LoadIcon(NULL, IDI_WINLOGO); + //window.window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + + if (RegisterClass(&window.window_class)) + { + int style = WS_VISIBLE|WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX; + + if (min_w != max_w && min_h != max_h) + style |= WS_SIZEBOX; + else + style |= WS_THICKFRAME; + + window.window_handle = CreateWindowEx(0, + window.window_class.lpszClassName, + name, + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + width, + height, + 0, + 0, + instance, + 0); + + if (window.window_handle) + { + window.hdc = GetDC(window.window_handle); + + PIXELFORMATDESCRIPTOR format; + memset(&format, 0, sizeof(PIXELFORMATDESCRIPTOR)); + format.nSize = sizeof(PIXELFORMATDESCRIPTOR); + format.nVersion = 1; + format.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + format.cColorBits = 24; + format.cAlphaBits = 8; + format.iLayerType = PFD_MAIN_PLANE; // PFD_TYPE_RGBA + s32 suggested_format_index = ChoosePixelFormat(window.hdc, &format); + + PIXELFORMATDESCRIPTOR actual_format; + DescribePixelFormat(window.hdc, suggested_format_index, sizeof(actual_format), &actual_format); + SetPixelFormat(window.hdc, suggested_format_index, &actual_format); + + window.gl_context = wglCreateContext(window.hdc); + + static HGLRC share_list = 0; + if (share_list == 0) + { + share_list = window.gl_context; + } + else + { + wglShareLists(share_list, window.gl_context); + } + + wglMakeCurrent(window.hdc, window.gl_context); + + ShowWindow(window.window_handle, cmd_show); + + // blending + glEnable(GL_DEPTH_TEST); + //glDepthMask(true); + //glClearDepth(50); + glDepthFunc(GL_LEQUAL); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // setup multisampling +#if 0 + glEnable(GL_ALPHA_TEST); + glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); + glEnable(GL_SAMPLE_ALPHA_TO_ONE); + glEnable(GL_MULTISAMPLE); + glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST); +#endif + + // https://stackoverflow.com/questions/5627229/sub-pixel-drawing-with-opengl + //glHint(GL_POINT_SMOOTH, GL_NICEST); + //glHint(GL_LINE_SMOOTH, GL_NICEST); + //glHint(GL_POLYGON_SMOOTH, GL_NICEST); + + //glEnable(GL_SMOOTH); + //glEnable(GL_POINT_SMOOTH); + //glEnable(GL_LINE_SMOOTH); + //glEnable(GL_POLYGON_SMOOTH); + ////////////////// + + window.is_open = true; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 1); + + //GLint m_viewport[4]; + //glGetIntegerv( GL_VIEWPORT, m_viewport ); + //printf("%d %d %d %d\n", m_viewport[0], m_viewport[1], m_viewport[2], m_viewport[3]); + + glMatrixMode(GL_MODELVIEW); + + TRACKMOUSEEVENT track; + track.cbSize = sizeof(track); + track.dwFlags = TME_LEAVE; + track.hwndTrack = window.window_handle; + TrackMouseEvent(&track); + } + else + { + platform_show_message(0, "An error occured within Windows, please restart the program.", "Error"); + abort(); + } + } + else + { + platform_show_message(0, "An error occured within Windows, please restart the program.", "Error"); + abort(); + } + + platform_get_focus(&window); + + return window; +} + +void platform_window_set_size(platform_window *window, u16 width, u16 height) +{ + s32 style = GetWindowLong(window->window_handle, GWL_STYLE); + BOOL menu = FALSE; + + RECT rec; + rec.left = 0; + rec.top = 0; + rec.right = width; + rec.bottom = height; + AdjustWindowRectEx(&rec, style, menu, 0); + SetWindowPos(window->window_handle,NULL,rec.left,rec.top,rec.right,rec.bottom,SWP_NOMOVE|SWP_NOZORDER); +} + +void platform_window_set_position(platform_window *window, u16 x, u16 y) +{ + RECT rec; + GetWindowRect(window->window_handle, &rec); + MoveWindow(window->window_handle, x, y, rec.right-rec.left, rec.bottom-rec.top, FALSE); +} + +bool platform_window_is_valid(platform_window *window) +{ + return window->hdc && window->window_handle; +} + +void platform_destroy_window(platform_window *window) +{ + wglMakeCurrent(NULL, NULL); + wglDeleteContext(window->gl_context); + + ReleaseDC(window->window_handle, window->hdc); + CloseWindow(window->window_handle); + DestroyWindow(window->window_handle); + UnregisterClassA(window->window_class.lpszClassName, instance); + window->hdc = 0; + window->window_handle = 0; +} + +void platform_handle_events(platform_window *window, mouse_input *mouse, keyboard_input *keyboard) +{ + current_window_to_handle = window; + current_keyboard_to_handle = keyboard; + current_mouse_to_handle = mouse; + + mouse->left_state &= ~MOUSE_CLICK; + mouse->right_state &= ~MOUSE_CLICK; + mouse->left_state &= ~MOUSE_DOUBLE_CLICK; + mouse->right_state &= ~MOUSE_DOUBLE_CLICK; + mouse->left_state &= ~MOUSE_RELEASE; + mouse->right_state &= ~MOUSE_RELEASE; + memset(keyboard->input_keys, 0, MAX_KEYCODE); + mouse->move_x = 0; + mouse->move_y = 0; + mouse->scroll_state = 0; + keyboard->text_changed = false; + + // mouse position (including outside of window) + current_window_to_handle->has_focus = GetFocus() == current_window_to_handle->window_handle; + + if (current_window_to_handle->has_focus) + { + if((GetKeyState(VK_LBUTTON) & 0x100) == 0) + { + current_mouse_to_handle->left_state = MOUSE_RELEASE; + } + + RECT rec; + GetWindowRect(window->window_handle, &rec); + POINT p; + GetCursorPos(&p); + mouse->x = p.x - rec.left - GetSystemMetrics(SM_CYSIZEFRAME); + mouse->y = p.y - rec.top - GetSystemMetrics(SM_CYSIZE) - GetSystemMetrics(SM_CYFRAME); + //printf("%d %d\n",GetSystemMetrics(SM_CYSIZE), GetSystemMetrics(SM_CYFRAME)); + } + + MSG message; + while(PeekMessageA(&message, window->window_handle, 0, 0, TRUE)) + { + TranslateMessage(&message); + SizingCheck(&message); + DispatchMessage(&message); + } + + glViewport(0, 0, window->width, window->height); +} + +void platform_window_swap_buffers(platform_window *window) +{ + // set cursor if changed + if (window->curr_cursor_type != window->next_cursor_type) + { + char *cursor_shape = 0; + switch(window->next_cursor_type) + { + case CURSOR_DEFAULT: cursor_shape = IDC_ARROW; break; + case CURSOR_POINTER: cursor_shape = IDC_HAND; break; + } + + HCURSOR cursor = LoadCursorA(NULL, cursor_shape); + + window->curr_cursor_type = window->next_cursor_type; + SetCursor(cursor); + } + + SwapBuffers(window->hdc); +} + +file_content platform_read_file_content(char *path, const char *mode) +{ + file_content result; + result.content = 0; + result.content_length = 0; + result.file_error = 0; + + FILE *file = fopen(path, mode); + if (!file) + { + if (errno == EMFILE) + result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS; + else if (errno == ENFILE) + result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM; + else if (errno == EACCES) + result.file_error = FILE_ERROR_NO_ACCESS; + else if (errno == EPERM) + result.file_error = FILE_ERROR_NO_ACCESS; + else if (errno == ENOENT) + result.file_error = FILE_ERROR_NOT_FOUND; + else if (errno == ECONNABORTED) + result.file_error = FILE_ERROR_CONNECTION_ABORTED; + else if (errno == ECONNREFUSED) + result.file_error = FILE_ERROR_CONNECTION_REFUSED; + else if (errno == ENETDOWN) + result.file_error = FILE_ERROR_NETWORK_DOWN; + else + { + result.file_error = FILE_ERROR_GENERIC; + printf("ERROR: %d\n", errno); + } + + goto done_failure; + } + + fseek(file, 0 , SEEK_END); + int length = ftell(file); + fseek(file, 0, SEEK_SET); + + s32 length_to_alloc = length+1; + + result.content = mem_alloc(length_to_alloc); + if (!result.content) goto done; + + memset(result.content, 0, length); + s32 read_result = fread(result.content, 1, length, file); + if (read_result == 0 && length != 0) + { + mem_free(result.content); + result.content = 0; + return result; + } + + result.content_length = read_result; + + ((char*)result.content)[length] = 0; + + done: + fclose(file); + done_failure: + return result; +} + +bool platform_write_file_content(char *path, const char *mode, char *buffer, s32 len) +{ + bool result = false; + + FILE *file = fopen(path, mode); + + if (!file) + { + goto done_failure; + } + else + { + fprintf(file, buffer); + } + + //done: + fclose(file); + done_failure: + return result; +} + +void platform_destroy_file_content(file_content *content) +{ + assert(content); + mem_free(content->content); +} + +bool get_active_directory(char *buffer) +{ + return GetCurrentDirectory(MAX_INPUT_LENGTH, buffer); +} + +bool set_active_directory(char *path) +{ + return SetCurrentDirectory(path); +} + +void platform_list_files_block(array *list, char *start_dir, array filters, bool recursive, memory_bucket *bucket, bool include_directories, bool *is_cancelled) +{ + assert(list); + s32 len = 0; + char *matched_filter = 0; + + char *subdirname_buf; + if (bucket) + subdirname_buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + subdirname_buf = mem_alloc(MAX_INPUT_LENGTH); + + char *start_dir_fix; + if (bucket) + start_dir_fix = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + start_dir_fix = mem_alloc(MAX_INPUT_LENGTH); + snprintf(start_dir_fix, MAX_INPUT_LENGTH, "%s*", start_dir); + + char *start_dir_clean; + if (bucket) + start_dir_clean = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + start_dir_clean = mem_alloc(MAX_INPUT_LENGTH); + string_copyn(start_dir_clean, start_dir, MAX_INPUT_LENGTH); + + WIN32_FIND_DATAA file_info; + HWND handle = FindFirstFileA(start_dir_fix, &file_info); + + if (!bucket) + mem_free(start_dir_fix); + + if (handle == INVALID_HANDLE_VALUE) + { + return; + } + + do + { + if (*is_cancelled) break; + char *name = file_info.cFileName; + + // symbolic link is not allowed.. + if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + continue; + + + if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) + continue; + + if (include_directories) + { + if ((len = filter_matches(&filters, name, + &matched_filter)) && len != -1) + { + // is file + char *buf; + if (bucket) + buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + buf = mem_alloc(MAX_INPUT_LENGTH); + snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, name); + + found_file f; + f.path = buf; + + if (bucket) + f.matched_filter= memory_bucket_reserve(bucket, len+1); + else + f.matched_filter= mem_alloc(len+1); + + string_copyn(f.matched_filter, matched_filter, len+1); + + mutex_lock(&list->mutex); + array_push_size(list, &f, sizeof(found_file)); + mutex_unlock(&list->mutex); + } + } + + if (recursive) + { + string_copyn(subdirname_buf, start_dir_clean, MAX_INPUT_LENGTH); + string_appendn(subdirname_buf, name, MAX_INPUT_LENGTH); + string_appendn(subdirname_buf, "\\", MAX_INPUT_LENGTH); + + // is directory + platform_list_files_block(list, subdirname_buf, filters, recursive, bucket, include_directories, is_cancelled); + } + } + else if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)) + { + if ((len = filter_matches(&filters, name, + &matched_filter)) && len != -1) + { + // is file + char *buf; + if (bucket) + buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH); + else + buf = mem_alloc(MAX_INPUT_LENGTH); + + snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, name); + + found_file f; + f.path = buf; + + if (bucket) + f.matched_filter = memory_bucket_reserve(bucket, len+1); + else + f.matched_filter = mem_alloc(len+1); + + string_copyn(f.matched_filter, matched_filter, len+1); + + mutex_lock(&list->mutex); + array_push_size(list, &f, sizeof(found_file)); + mutex_unlock(&list->mutex); + } + } + } + while (FindNextFile(handle, &file_info) != 0); + + if (!bucket) + mem_free(start_dir_clean); + + FindClose(handle); +} + +static void* platform_open_file_dialog_implementation(void *data) +{ + struct open_dialog_args *args = data; + + OPENFILENAME info; + info.lStructSize = sizeof(OPENFILENAME); + info.hwndOwner = NULL; + info.hInstance = NULL; + + char filter[MAX_INPUT_LENGTH]; + memset(filter, 0, MAX_INPUT_LENGTH); + + if (args->file_filter) + { + string_copyn(filter, args->file_filter, MAX_INPUT_LENGTH); + filter[strlen(filter)] = 0; + filter[strlen(filter)+1] = 0; + info.lpstrFilter = filter; + } + else + { + info.lpstrFilter = NULL; + } + + char szFile[MAX_INPUT_LENGTH]; + memset(szFile, 0, MAX_INPUT_LENGTH); + + info.lpstrCustomFilter = NULL; + info.nMaxCustFilter = MAX_INPUT_LENGTH; + info.nFilterIndex = 0; + info.lpstrFile = (char*)szFile; + info.nMaxFile = MAX_INPUT_LENGTH; + + info.lpstrDefExt = args->default_save_file_extension; + + info.lpstrFileTitle = NULL; + info.lpstrInitialDir = args->start_path; + info.lpstrTitle = NULL; + + if (args->type == SAVE_FILE) + { + info.Flags = OFN_EXTENSIONDIFFERENT | OFN_OVERWRITEPROMPT; + GetSaveFileNameA(&info); + string_copyn(args->buffer, info.lpstrFile, MAX_INPUT_LENGTH); + } + else if (args->type == OPEN_DIRECTORY) + { + BROWSEINFOA inf; + PIDLIST_ABSOLUTE result = SHBrowseForFolderA(&inf); + if (!result) return 0; + + SHGetPathFromIDListA(result, args->buffer); + } + else if (args->type == OPEN_FILE) + { + info.Flags = OFN_FILEMUSTEXIST; + GetOpenFileNameA(&info); + string_copyn(args->buffer, info.lpstrFile, MAX_INPUT_LENGTH); + } + + return 0; +} + +void *platform_open_file_dialog_block(void *arg) +{ + platform_open_file_dialog_implementation(arg); + mem_free(arg); + return 0; +} + +char *platform_get_full_path(char *file) +{ + char *buf = mem_alloc(MAX_INPUT_LENGTH); + if (!GetFullPathNameA(file, MAX_INPUT_LENGTH, buf, NULL)) + { + buf[0] = 0; + } + + return buf; +} + +void platform_open_url(char *command) +{ + platform_run_command(command); +} + +void platform_run_command(char *command) +{ + // might be start instead of open + ShellExecuteA(NULL, "open", command, NULL, NULL, SW_SHOWDEFAULT); +} + +void platform_window_make_current(platform_window *window) +{ + wglMakeCurrent(window->hdc, window->gl_context); +} + +void platform_init(int argc, char **argv) +{ + setlocale(LC_ALL, "en_US.UTF-8"); + + QueryPerformanceFrequency(&perf_frequency); + CoInitialize(NULL); + create_key_tables(); + + instance = GetModuleHandle(NULL); + cmd_show = argc; + + // get fullpath of the directory the exe is residing in + binary_path = platform_get_full_path(argv[0]); + + platform_create_config_directory(); + + char buf[MAX_INPUT_LENGTH]; + get_directory_from_path(buf, binary_path); + string_copyn(binary_path, buf, MAX_INPUT_LENGTH); + + assets_create(); +} + +void platform_set_icon(platform_window *window, image *img) +{ + BYTE *bmp; + s32 data_len = img->width * img->height * 4; + s32 total_len = data_len + 40 * 4; + + bmp = mem_alloc(total_len); + + struct { + int32_t header_size, width, geight; + int16_t color_plane, bits_per_pixel; + int32_t compression_mode, img_length, obsolete[4]; + } bmp_header = {40, img->width, img->height * 2, 1, 32, BI_RGB, data_len, {0,0,0,0} }; + + memcpy(bmp, &bmp_header, 40); + + s32 index = 0; + for (s32 y = img->height-1; y >= 0; y--) + { + for (s32 x = 0; x < img->width; x++) + { + s32 img_pixel = *(((s32*)img->data+(x+(y*img->width)))); + + // 0xAABBGGRR + s32 a = (img_pixel>>24) & 0x000000FF; + s32 b = (img_pixel>>16) & 0x000000FF; + s32 g = (img_pixel>> 8) & 0x000000FF; + s32 r = (img_pixel>> 0) & 0x000000FF; + + //s32 c = (r << 24) | (g << 16) | (b << 8) | (a << 0); + s32 c = (a << 24) | (r << 16) | (g << 8) | (b << 0); + memcpy(bmp+40+(index*4), &c, 4); + + ++index; + } + } + + HICON icon = CreateIconFromResourceEx(bmp, total_len, TRUE, 0x00030000, img->width, img->height, LR_DEFAULTCOLOR); + + SendMessage(window->window_handle, WM_SETICON, ICON_SMALL, (LPARAM)icon); + SendMessage(window->window_handle, WM_SETICON, ICON_BIG, (LPARAM)icon); + + mem_free(bmp); + + if (!icon) + printf("Failed to load icon, error code: %ld.\n", GetLastError()); +} + +u64 platform_get_time(time_type time_type, time_precision precision) +{ + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + + double sec = counter.QuadPart / (double)(perf_frequency.QuadPart); + + //printf("%I64d %I64d %f\n", counter.QuadPart, perf_frequency.QuadPart, sec); + + double val = sec; + + if (precision == TIME_NS) + { + return val*1000000000; + } + if (precision == TIME_US) + { + return val*1000000; + } + if (precision == TIME_MILI_S) + { + return val*1000; + } + if (precision == TIME_S) + { + return val; + } + return val; +} + +s32 platform_get_memory_size() +{ + u64 result; + GetPhysicallyInstalledSystemMemory(&result); + return result; +} + +s32 platform_get_cpu_count() +{ + SYSTEM_INFO info; + GetSystemInfo(&info); + + return info.dwNumberOfProcessors; +} + +u64 string_to_u64(char *str) +{ + return (u64)strtoull(str, 0, 10); +} + +u32 string_to_u32(char *str) +{ + return (u32)strtoul(str, 0, 10); +} + +u16 string_to_u16(char *str) +{ + return (u16)strtoul(str, 0, 10); +} + +u8 string_to_u8(char *str) +{ + return (u8)strtoul(str, 0, 10); +} + +s64 string_to_s64(char *str) +{ + return (s64)strtoull(str, 0, 10); +} + +s32 string_to_s32(char *str) +{ + return (s32)strtoul(str, 0, 10); +} + +s16 string_to_s16(char *str) +{ + return (s16)strtoul(str, 0, 10); +} + +s8 string_to_s8(char *str) +{ + return (s8)strtoul(str, 0, 10); +}
\ No newline at end of file diff --git a/src/windows/thread.c b/src/windows/thread.c new file mode 100644 index 0000000..9669158 --- /dev/null +++ b/src/windows/thread.c @@ -0,0 +1,102 @@ +/* +* BSD 2-Clause “Simplified” License +* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com +* All rights reserved. +*/ + +#include <synchapi.h> + +thread thread_start(void *(*start_routine) (void *), void *arg) +{ + thread result; + result.valid = false; + + result.thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, + arg, 0, NULL); + result.valid = true; + + return result; +} + +void thread_join(thread *thread) +{ + if (thread->valid) + { + WaitForSingleObject(thread->thread, INFINITE); + CloseHandle(thread->thread); + } +} + +bool thread_tryjoin(thread *thread) +{ + if (thread->valid) + { + s32 result = WaitForSingleObject(thread->thread, 0); + return result == WAIT_OBJECT_0; + } + return false; +} + +void thread_detach(thread *thread) +{ + if (thread->valid) + { + CloseHandle(thread->thread); + } +} + +void thread_stop(thread *thread) +{ + if (thread->valid) + { + SuspendThread(thread->thread); + } +} + +u32 thread_get_id() +{ + return GetCurrentThreadId(); +} + +void thread_sleep(u64 microseconds) +{ + Sleep(microseconds/1000); +} + +mutex mutex_create() +{ + mutex result; + result.mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + return result; +} + +mutex mutex_create_recursive() +{ + return mutex_create(); +} + +void mutex_lock(mutex *mutex) +{ + WaitForSingleObject( + mutex->mutex, // handle to mutex + INFINITE); // no time-out interval +} + +bool mutex_trylock(mutex *mutex) +{ + return WaitForSingleObject(mutex->mutex, 1) == WAIT_OBJECT_0; +} + +void mutex_unlock(mutex *mutex) +{ + ReleaseMutex(mutex->mutex); +} + +void mutex_destroy(mutex *mutex) +{ + CloseHandle(mutex->mutex); +} |
