diff --git a/CMakeLists.txt b/CMakeLists.txt
index 84aacb9..e682af0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@ add_library(Wellspring
#Public header
include/Wellspring.h
#Source
+ lib/json.h
lib/stb_rect_pack.h
lib/stb_truetype.h
src/Wellspring.c
diff --git a/README.md b/README.md
index 353b7a2..1536340 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,21 @@
-This is Wellspring, an immediate mode font rendering system in C.
+This is Wellspring, an immediate mode multiple-channel signed distance field font rendering system in C.
About Wellspring
----------------
Wellspring is inspired by the design of Dear ImGui.
-It outputs pixel data that you can upload to a texture and vertex buffers that you can render anytime in your 3D application.
+It outputs buffer data that you can upload and render anytime in your 3D application.
This means that you can integrate it easily using the graphics library of your choice.
-Wellspring uses stb_truetype to rasterize and pack fonts quickly.
+
+Wellspring uses JSON output from [msdf-atlas-gen](https://github.com/Chlumsky/msdf-atlas-gen) to generate buffers. It also uses [stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h) for additional kerning support. At render time, bind the image data from msdf-atlas-gen with buffers generated by Wellspring for beautiful MSDF font rendering.
+
+Using msdf-atlas-gen
+--------------
+A full explanation of msdf-atlas-gen is beyond the scope of this project, but note that Wellspring only accepts MSDF atlas types with JSON output.
+
+Your atlas generation might look like this:
+```sh
+msdf-atlas-gen -yorigin top -font ~/mygame/myfont.otf -imageout ~/mygame/content/forgotten_dream.png -json ~/mygame/content/forgotten_dream.json
+```
Dependencies
------------
@@ -20,7 +30,7 @@ For *nix platforms, use CMake:
$ cmake ../
$ make
-For Windows, see the 'visualc/' directory.
+For Windows, you can use cmake-gui to generate a Visual Studio solution or use VSCode with the CMake and C/C++ Tools extensions.
License
-------
diff --git a/include/Wellspring.h b/include/Wellspring.h
index 56295a0..71e2133 100644
--- a/include/Wellspring.h
+++ b/include/Wellspring.h
@@ -63,7 +63,6 @@ WELLSPRINGAPI uint32_t Wellspring_LinkedVersion(void);
/* Type definitions */
typedef struct Wellspring_Font Wellspring_Font;
-typedef struct Wellspring_Packer Wellspring_Packer;
typedef struct Wellspring_TextBatch Wellspring_TextBatch;
typedef struct Wellspring_FontRange
@@ -113,46 +112,25 @@ typedef enum Wellspring_VerticalAlignment
WELLSPRINGAPI Wellspring_Font* Wellspring_CreateFont(
const uint8_t *fontBytes,
- uint32_t fontBytesLength
-);
-
-WELLSPRINGAPI Wellspring_Packer* Wellspring_CreatePacker(
- Wellspring_Font *font,
- float fontSize,
- uint32_t width,
- uint32_t height,
- uint32_t strideInBytes, /* 0 means the buffer is tightly packed. */
- uint32_t padding /* A sensible value here is 1 to allow bilinear filtering. */
-);
-
-WELLSPRINGAPI uint32_t Wellspring_PackFontRanges(
- Wellspring_Packer *packer,
- Wellspring_FontRange *ranges,
- uint32_t numRanges
-);
-
-/* Returns a pointer to an array of rasterized pixels of the packed font.
- * This data must be uploaded to a texture before you render!
- * The pixel data becomes outdated if you call PackFontRanges.
- * Length is width * height.
- */
-WELLSPRINGAPI uint8_t* Wellspring_GetPixelDataPointer(
- Wellspring_Packer *packer
+ uint32_t fontBytesLength,
+ const uint8_t *atlasJsonBytes,
+ uint32_t atlasJsonBytesLength,
+ float *pPixelsPerEm,
+ float *pDistanceRange
);
/* Batches are not thread-safe, recommend one batch per thread. */
-WELLSPRINGAPI Wellspring_TextBatch* Wellspring_CreateTextBatch();
+WELLSPRINGAPI Wellspring_TextBatch* Wellspring_CreateTextBatch(void);
/* Also restarts the batch */
WELLSPRINGAPI void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch,
- Wellspring_Packer *packer
+ Wellspring_Font *font
);
WELLSPRINGAPI uint8_t Wellspring_TextBounds(
- Wellspring_Packer* packer,
- float x,
- float y,
+ Wellspring_Font *font,
+ int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes,
@@ -160,11 +138,9 @@ WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_Rectangle *pRectangle
);
-WELLSPRINGAPI uint8_t Wellspring_Draw(
+WELLSPRINGAPI uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch,
- float x,
- float y,
- float depth,
+ int pixelSize,
Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
@@ -182,7 +158,6 @@ WELLSPRINGAPI void Wellspring_GetBufferData(
);
WELLSPRINGAPI void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch);
-WELLSPRINGAPI void Wellspring_DestroyPacker(Wellspring_Packer *packer);
WELLSPRINGAPI void Wellspring_DestroyFont(Wellspring_Font *font);
#ifdef __cplusplus
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 0000000..8eecae3
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,3455 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/json.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 .
+*/
+
+#ifndef SHEREDOM_JSON_H_INCLUDED
+#define SHEREDOM_JSON_H_INCLUDED
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+
+/* disable warning: no function prototype given: converting '()' to '(void)' */
+#pragma warning(disable : 4255)
+
+/* disable warning: '__cplusplus' is not defined as a preprocessor macro,
+ * replacing with '0' for '#if/#elif' */
+#pragma warning(disable : 4668)
+
+/* disable warning: 'bytes padding added after construct' */
+#pragma warning(disable : 4820)
+#endif
+
+#include
+#include
+
+#if defined(_MSC_VER) || defined(__WATCOMC__)
+#define json_weak __inline
+#elif defined(__clang__) || defined(__GNUC__)
+#define json_weak __attribute__((weak))
+#else
+#error Non clang, non gcc, non MSVC, non WATCOM compiler found!
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct json_value_s;
+struct json_parse_result_s;
+
+enum json_parse_flags_e {
+ json_parse_flags_default = 0,
+
+ /* allow trailing commas in objects and arrays. For example, both [true,] and
+ {"a" : null,} would be allowed with this option on. */
+ json_parse_flags_allow_trailing_comma = 0x1,
+
+ /* allow unquoted keys for objects. For example, {a : null} would be allowed
+ with this option on. */
+ json_parse_flags_allow_unquoted_keys = 0x2,
+
+ /* allow a global unbracketed object. For example, a : null, b : true, c : {}
+ would be allowed with this option on. */
+ json_parse_flags_allow_global_object = 0x4,
+
+ /* allow objects to use '=' instead of ':' between key/value pairs. For
+ example, a = null, b : true would be allowed with this option on. */
+ json_parse_flags_allow_equals_in_object = 0x8,
+
+ /* allow that objects don't have to have comma separators between key/value
+ pairs. */
+ json_parse_flags_allow_no_commas = 0x10,
+
+ /* allow c-style comments (either variants) to be ignored in the input JSON
+ file. */
+ json_parse_flags_allow_c_style_comments = 0x20,
+
+ /* deprecated flag, unused. */
+ json_parse_flags_deprecated = 0x40,
+
+ /* record location information for each value. */
+ json_parse_flags_allow_location_information = 0x80,
+
+ /* allow strings to be 'single quoted'. */
+ json_parse_flags_allow_single_quoted_strings = 0x100,
+
+ /* allow numbers to be hexadecimal. */
+ json_parse_flags_allow_hexadecimal_numbers = 0x200,
+
+ /* allow numbers like +123 to be parsed. */
+ json_parse_flags_allow_leading_plus_sign = 0x400,
+
+ /* allow numbers like .0123 or 123. to be parsed. */
+ json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800,
+
+ /* allow Infinity, -Infinity, NaN, -NaN. */
+ json_parse_flags_allow_inf_and_nan = 0x1000,
+
+ /* allow multi line string values. */
+ json_parse_flags_allow_multi_line_strings = 0x2000,
+
+ /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set
+ of other parsing options. */
+ json_parse_flags_allow_simplified_json =
+ (json_parse_flags_allow_trailing_comma |
+ json_parse_flags_allow_unquoted_keys |
+ json_parse_flags_allow_global_object |
+ json_parse_flags_allow_equals_in_object |
+ json_parse_flags_allow_no_commas),
+
+ /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing
+ options. */
+ json_parse_flags_allow_json5 =
+ (json_parse_flags_allow_trailing_comma |
+ json_parse_flags_allow_unquoted_keys |
+ json_parse_flags_allow_c_style_comments |
+ json_parse_flags_allow_single_quoted_strings |
+ json_parse_flags_allow_hexadecimal_numbers |
+ json_parse_flags_allow_leading_plus_sign |
+ json_parse_flags_allow_leading_or_trailing_decimal_point |
+ json_parse_flags_allow_inf_and_nan |
+ json_parse_flags_allow_multi_line_strings)
+};
+
+/* Parse a JSON text file, returning a pointer to the root of the JSON
+ * structure. json_parse performs 1 call to malloc for the entire encoding.
+ * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */
+json_weak struct json_value_s *json_parse(const void *src, size_t src_size);
+
+/* Parse a JSON text file, returning a pointer to the root of the JSON
+ * structure. json_parse performs 1 call to alloc_func_ptr for the entire
+ * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc
+ * failed). If an error occurred, the result struct (if not NULL) will explain
+ * the type of error, and the location in the input it occurred. If
+ * alloc_func_ptr is null then malloc is used. */
+json_weak struct json_value_s *
+json_parse_ex(const void *src, size_t src_size, size_t flags_bitset,
+ void *(*alloc_func_ptr)(void *, size_t), void *user_data,
+ struct json_parse_result_s *result);
+
+/* Extracts a value and all the data that makes it up into a newly created
+ * value. json_extract_value performs 1 call to malloc for the entire encoding.
+ */
+json_weak struct json_value_s *
+json_extract_value(const struct json_value_s *value);
+
+/* Extracts a value and all the data that makes it up into a newly created
+ * value. json_extract_value performs 1 call to alloc_func_ptr for the entire
+ * encoding. If alloc_func_ptr is null then malloc is used. */
+json_weak struct json_value_s *
+json_extract_value_ex(const struct json_value_s *value,
+ void *(*alloc_func_ptr)(void *, size_t), void *user_data);
+
+/* Write out a minified JSON utf-8 string. This string is an encoding of the
+ * minimal string characters required to still encode the same data.
+ * json_write_minified performs 1 call to malloc for the entire encoding. Return
+ * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size
+ * parameter is optional as the utf-8 string is null terminated. */
+json_weak void *json_write_minified(const struct json_value_s *value,
+ size_t *out_size);
+
+/* Write out a pretty JSON utf-8 string. This string is encoded such that the
+ * resultant JSON is pretty in that it is easily human readable. The indent and
+ * newline parameters allow a user to specify what kind of indentation and
+ * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both
+ * indent and newline can be NULL, indent defaults to two spaces (" "), and
+ * newline defaults to linux newlines ('\n' as the newline character).
+ * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0
+ * if an error occurred (malformed JSON input, or malloc failed). The out_size
+ * parameter is optional as the utf-8 string is null terminated. */
+json_weak void *json_write_pretty(const struct json_value_s *value,
+ const char *indent, const char *newline,
+ size_t *out_size);
+
+/* Reinterpret a JSON value as a string. Returns null is the value was not a
+ * string. */
+json_weak struct json_string_s *
+json_value_as_string(struct json_value_s *const value);
+
+/* Reinterpret a JSON value as a number. Returns null is the value was not a
+ * number. */
+json_weak struct json_number_s *
+json_value_as_number(struct json_value_s *const value);
+
+/* Reinterpret a JSON value as an object. Returns null is the value was not an
+ * object. */
+json_weak struct json_object_s *
+json_value_as_object(struct json_value_s *const value);
+
+/* Reinterpret a JSON value as an array. Returns null is the value was not an
+ * array. */
+json_weak struct json_array_s *
+json_value_as_array(struct json_value_s *const value);
+
+/* Whether the value is true. */
+json_weak int json_value_is_true(const struct json_value_s *const value);
+
+/* Whether the value is false. */
+json_weak int json_value_is_false(const struct json_value_s *const value);
+
+/* Whether the value is null. */
+json_weak int json_value_is_null(const struct json_value_s *const value);
+
+/* The various types JSON values can be. Used to identify what a value is. */
+typedef enum json_type_e {
+ json_type_string,
+ json_type_number,
+ json_type_object,
+ json_type_array,
+ json_type_true,
+ json_type_false,
+ json_type_null
+
+} json_type_t;
+
+/* A JSON string value. */
+typedef struct json_string_s {
+ /* utf-8 string */
+ const char *string;
+ /* The size (in bytes) of the string */
+ size_t string_size;
+
+} json_string_t;
+
+/* A JSON string value (extended). */
+typedef struct json_string_ex_s {
+ /* The JSON string this extends. */
+ struct json_string_s string;
+
+ /* The character offset for the value in the JSON input. */
+ size_t offset;
+
+ /* The line number for the value in the JSON input. */
+ size_t line_no;
+
+ /* The row number for the value in the JSON input, in bytes. */
+ size_t row_no;
+
+} json_string_ex_t;
+
+/* A JSON number value. */
+typedef struct json_number_s {
+ /* ASCII string containing representation of the number. */
+ const char *number;
+ /* the size (in bytes) of the number. */
+ size_t number_size;
+
+} json_number_t;
+
+/* an element of a JSON object. */
+typedef struct json_object_element_s {
+ /* the name of this element. */
+ struct json_string_s *name;
+ /* the value of this element. */
+ struct json_value_s *value;
+ /* the next object element (can be NULL if the last element in the object). */
+ struct json_object_element_s *next;
+
+} json_object_element_t;
+
+/* a JSON object value. */
+typedef struct json_object_s {
+ /* a linked list of the elements in the object. */
+ struct json_object_element_s *start;
+ /* the number of elements in the object. */
+ size_t length;
+
+} json_object_t;
+
+/* an element of a JSON array. */
+typedef struct json_array_element_s {
+ /* the value of this element. */
+ struct json_value_s *value;
+ /* the next array element (can be NULL if the last element in the array). */
+ struct json_array_element_s *next;
+
+} json_array_element_t;
+
+/* a JSON array value. */
+typedef struct json_array_s {
+ /* a linked list of the elements in the array. */
+ struct json_array_element_s *start;
+ /* the number of elements in the array. */
+ size_t length;
+
+} json_array_t;
+
+/* a JSON value. */
+typedef struct json_value_s {
+ /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */
+ /* json_array_s. Should be cast to the appropriate struct type based on what.
+ */
+ /* the type of this value is. */
+ void *payload;
+ /* must be one of json_type_e. If type is json_type_true, json_type_false, or.
+ */
+ /* json_type_null, payload will be NULL. */
+ size_t type;
+
+} json_value_t;
+
+/* a JSON value (extended). */
+typedef struct json_value_ex_s {
+ /* the JSON value this extends. */
+ struct json_value_s value;
+
+ /* the character offset for the value in the JSON input. */
+ size_t offset;
+
+ /* the line number for the value in the JSON input. */
+ size_t line_no;
+
+ /* the row number for the value in the JSON input, in bytes. */
+ size_t row_no;
+
+} json_value_ex_t;
+
+/* a parsing error code. */
+enum json_parse_error_e {
+ /* no error occurred (huzzah!). */
+ json_parse_error_none = 0,
+
+ /* expected either a comma or a closing '}' or ']' to close an object or. */
+ /* array! */
+ json_parse_error_expected_comma_or_closing_bracket,
+
+ /* colon separating name/value pair was missing! */
+ json_parse_error_expected_colon,
+
+ /* expected string to begin with '"'! */
+ json_parse_error_expected_opening_quote,
+
+ /* invalid escaped sequence in string! */
+ json_parse_error_invalid_string_escape_sequence,
+
+ /* invalid number format! */
+ json_parse_error_invalid_number_format,
+
+ /* invalid value! */
+ json_parse_error_invalid_value,
+
+ /* reached end of buffer before object/array was complete! */
+ json_parse_error_premature_end_of_buffer,
+
+ /* string was malformed! */
+ json_parse_error_invalid_string,
+
+ /* a call to malloc, or a user provider allocator, failed. */
+ json_parse_error_allocator_failed,
+
+ /* the JSON input had unexpected trailing characters that weren't part of the.
+ JSON value. */
+ json_parse_error_unexpected_trailing_characters,
+
+ /* catch-all error for everything else that exploded (real bad chi!). */
+ json_parse_error_unknown
+};
+
+/* error report from json_parse_ex(). */
+typedef struct json_parse_result_s {
+ /* the error code (one of json_parse_error_e). */
+ size_t error;
+
+ /* the character offset for the error in the JSON input. */
+ size_t error_offset;
+
+ /* the line number for the error in the JSON input. */
+ size_t error_line_no;
+
+ /* the row number for the error, in bytes. */
+ size_t error_row_no;
+
+} json_parse_result_t;
+
+#ifdef __cplusplus
+} /* extern "C". */
+#endif
+
+#ifndef SHEREDOM_JSON_H_malloc
+#include
+#define SHEREDOM_JSON_H_malloc(x) (malloc(x))
+#define SHEREDOM_JSON_H_free(x) (free(x))
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1920)
+#define json_uintmax_t unsigned __int64
+#else
+#include
+#define json_uintmax_t uintmax_t
+#endif
+
+#if defined(_MSC_VER)
+#define json_strtoumax _strtoui64
+#else
+#define json_strtoumax strtoumax
+#endif
+
+#if defined(__cplusplus) && (__cplusplus >= 201103L)
+#define json_null nullptr
+#else
+#define json_null 0
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+
+/* we do one big allocation via malloc, then cast aligned slices of this for. */
+/* our structures - we don't have a way to tell the compiler we know what we. */
+/* are doing, so disable the warning instead! */
+#pragma clang diagnostic ignored "-Wcast-align"
+
+/* We use C style casts everywhere. */
+#pragma clang diagnostic ignored "-Wold-style-cast"
+
+/* We need long long for strtoull. */
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+
+/* Who cares if nullptr doesn't work with C++98, we don't use it there! */
+#pragma clang diagnostic ignored "-Wc++98-compat"
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#elif defined(_MSC_VER)
+#pragma warning(push)
+
+/* disable 'function selected for inline expansion' warning. */
+#pragma warning(disable : 4711)
+
+/* disable '#pragma warning: there is no warning number' warning. */
+#pragma warning(disable : 4619)
+
+/* disable 'warning number not a valid compiler warning' warning. */
+#pragma warning(disable : 4616)
+
+/* disable 'Compiler will insert Spectre mitigation for memory load if
+ * /Qspectre. */
+/* switch specified' warning. */
+#pragma warning(disable : 5045)
+#endif
+
+struct json_parse_state_s {
+ const char *src;
+ size_t size;
+ size_t offset;
+ size_t flags_bitset;
+ char *data;
+ char *dom;
+ size_t dom_size;
+ size_t data_size;
+ size_t line_no; /* line counter for error reporting. */
+ size_t line_offset; /* (offset-line_offset) is the character number (in
+ bytes). */
+ size_t error;
+};
+
+json_weak int json_hexadecimal_digit(const char c);
+int json_hexadecimal_digit(const char c) {
+ if ('0' <= c && c <= '9') {
+ return c - '0';
+ }
+ if ('a' <= c && c <= 'f') {
+ return c - 'a' + 10;
+ }
+ if ('A' <= c && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ return -1;
+}
+
+json_weak int json_hexadecimal_value(const char *c, const unsigned long size,
+ unsigned long *result);
+int json_hexadecimal_value(const char *c, const unsigned long size,
+ unsigned long *result) {
+ const char *p;
+ int digit;
+
+ if (size > sizeof(unsigned long) * 2) {
+ return 0;
+ }
+
+ *result = 0;
+ for (p = c; (unsigned long)(p - c) < size; ++p) {
+ *result <<= 4;
+ digit = json_hexadecimal_digit(*p);
+ if (digit < 0 || digit > 15) {
+ return 0;
+ }
+ *result |= (unsigned char)digit;
+ }
+ return 1;
+}
+
+json_weak int json_skip_whitespace(struct json_parse_state_s *state);
+int json_skip_whitespace(struct json_parse_state_s *state) {
+ size_t offset = state->offset;
+ const size_t size = state->size;
+ const char *const src = state->src;
+
+ if (offset >= state->size) {
+ return 0;
+ }
+
+ /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and
+ * '\t'. */
+ switch (src[offset]) {
+ default:
+ return 0;
+ case ' ':
+ case '\r':
+ case '\t':
+ case '\n':
+ break;
+ }
+
+ do {
+ switch (src[offset]) {
+ default:
+ /* Update offset. */
+ state->offset = offset;
+ return 1;
+ case ' ':
+ case '\r':
+ case '\t':
+ break;
+ case '\n':
+ state->line_no++;
+ state->line_offset = offset;
+ break;
+ }
+
+ offset++;
+ } while (offset < size);
+
+ /* Update offset. */
+ state->offset = offset;
+ return 1;
+}
+
+json_weak int json_skip_c_style_comments(struct json_parse_state_s *state);
+int json_skip_c_style_comments(struct json_parse_state_s *state) {
+ /* to have a C-style comment we need at least 2 characters of space */
+ if ((state->offset + 2) > state->size) {
+ return 0;
+ }
+
+ /* do we have a comment? */
+ if ('/' == state->src[state->offset]) {
+ if ('/' == state->src[state->offset + 1]) {
+ /* we had a comment of the form // */
+
+ /* skip first '/' */
+ state->offset++;
+
+ /* skip second '/' */
+ state->offset++;
+
+ while (state->offset < state->size) {
+ switch (state->src[state->offset]) {
+ default:
+ /* skip the character in the comment */
+ state->offset++;
+ break;
+ case '\n':
+ /* if we have a newline, our comment has ended! Skip the newline */
+ state->offset++;
+
+ /* we entered a newline, so move our line info forward */
+ state->line_no++;
+ state->line_offset = state->offset;
+ return 1;
+ }
+ }
+
+ /* we reached the end of the JSON file! */
+ return 1;
+ } else if ('*' == state->src[state->offset + 1]) {
+ /* we had a comment in the C-style long form */
+
+ /* skip '/' */
+ state->offset++;
+
+ /* skip '*' */
+ state->offset++;
+
+ while (state->offset + 1 < state->size) {
+ if (('*' == state->src[state->offset]) &&
+ ('/' == state->src[state->offset + 1])) {
+ /* we reached the end of our comment! */
+ state->offset += 2;
+ return 1;
+ } else if ('\n' == state->src[state->offset]) {
+ /* we entered a newline, so move our line info forward */
+ state->line_no++;
+ state->line_offset = state->offset;
+ }
+
+ /* skip character within comment */
+ state->offset++;
+ }
+
+ /* comment wasn't ended correctly which is a failure */
+ return 1;
+ }
+ }
+
+ /* we didn't have any comment, which is ok too! */
+ return 0;
+}
+
+json_weak int json_skip_all_skippables(struct json_parse_state_s *state);
+int json_skip_all_skippables(struct json_parse_state_s *state) {
+ /* skip all whitespace and other skippables until there are none left. note
+ * that the previous version suffered from read past errors should. the
+ * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag.
+ */
+
+ int did_consume = 0;
+ const size_t size = state->size;
+
+ if (json_parse_flags_allow_c_style_comments & state->flags_bitset) {
+ do {
+ if (state->offset == size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ did_consume = json_skip_whitespace(state);
+
+ /* This should really be checked on access, not in front of every call.
+ */
+ if (state->offset >= size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ did_consume |= json_skip_c_style_comments(state);
+ } while (0 != did_consume);
+ } else {
+ do {
+ if (state->offset == size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ did_consume = json_skip_whitespace(state);
+ } while (0 != did_consume);
+ }
+
+ if (state->offset == size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ return 0;
+}
+
+json_weak int json_get_value_size(struct json_parse_state_s *state,
+ int is_global_object);
+
+json_weak int json_get_string_size(struct json_parse_state_s *state,
+ size_t is_key);
+int json_get_string_size(struct json_parse_state_s *state, size_t is_key) {
+ size_t offset = state->offset;
+ const size_t size = state->size;
+ size_t data_size = 0;
+ const char *const src = state->src;
+ const int is_single_quote = '\'' == src[offset];
+ const char quote_to_use = is_single_quote ? '\'' : '"';
+ const size_t flags_bitset = state->flags_bitset;
+ unsigned long codepoint;
+ unsigned long high_surrogate = 0;
+
+ if ((json_parse_flags_allow_location_information & flags_bitset) != 0 &&
+ is_key != 0) {
+ state->dom_size += sizeof(struct json_string_ex_s);
+ } else {
+ state->dom_size += sizeof(struct json_string_s);
+ }
+
+ if ('"' != src[offset]) {
+ /* if we are allowed single quoted strings check for that too. */
+ if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) &&
+ is_single_quote)) {
+ state->error = json_parse_error_expected_opening_quote;
+ state->offset = offset;
+ return 1;
+ }
+ }
+
+ /* skip leading '"' or '\''. */
+ offset++;
+
+ while ((offset < size) && (quote_to_use != src[offset])) {
+ /* add space for the character. */
+ data_size++;
+
+ switch (src[offset]) {
+ default:
+ break;
+ case '\0':
+ case '\t':
+ state->error = json_parse_error_invalid_string;
+ state->offset = offset;
+ return 1;
+ }
+
+ if ('\\' == src[offset]) {
+ /* skip reverse solidus character. */
+ offset++;
+
+ if (offset == size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ state->offset = offset;
+ return 1;
+ }
+
+ switch (src[offset]) {
+ default:
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ /* all valid characters! */
+ offset++;
+ break;
+ case 'u':
+ if (!(offset + 5 < size)) {
+ /* invalid escaped unicode sequence! */
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ }
+
+ codepoint = 0;
+ if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) {
+ /* escaped unicode sequences must contain 4 hexadecimal digits! */
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ }
+
+ /* Valid sequence!
+ * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points.
+ * 1 7 U + 0000 U + 007F 0xxxxxxx.
+ * 2 11 U + 0080 U + 07FF 110xxxxx
+ * 10xxxxxx.
+ * 3 16 U + 0800 U + FFFF 1110xxxx
+ * 10xxxxxx 10xxxxxx.
+ * 4 21 U + 10000 U + 10FFFF 11110xxx
+ * 10xxxxxx 10xxxxxx 10xxxxxx.
+ * Note: the high and low surrogate halves used by UTF-16 (U+D800
+ * through U+DFFF) and code points not encodable by UTF-16 (those after
+ * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must
+ * be treated as an invalid byte sequence. */
+
+ if (high_surrogate != 0) {
+ /* we previously read the high half of the \uxxxx\uxxxx pair, so now
+ * we expect the low half. */
+ if (codepoint >= 0xdc00 &&
+ codepoint <= 0xdfff) { /* low surrogate range. */
+ data_size += 3;
+ high_surrogate = 0;
+ } else {
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ }
+ } else if (codepoint <= 0x7f) {
+ data_size += 0;
+ } else if (codepoint <= 0x7ff) {
+ data_size += 1;
+ } else if (codepoint >= 0xd800 &&
+ codepoint <= 0xdbff) { /* high surrogate range. */
+ /* The codepoint is the first half of a "utf-16 surrogate pair". so we
+ * need the other half for it to be valid: \uHHHH\uLLLL. */
+ if (offset + 11 > size || '\\' != src[offset + 5] ||
+ 'u' != src[offset + 6]) {
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ }
+ high_surrogate = codepoint;
+ } else if (codepoint >= 0xd800 &&
+ codepoint <= 0xdfff) { /* low surrogate range. */
+ /* we did not read the other half before. */
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ } else {
+ data_size += 2;
+ }
+ /* escaped codepoints after 0xffff are supported in json through utf-16
+ * surrogate pairs: \uD83D\uDD25 for U+1F525. */
+
+ offset += 5;
+ break;
+ }
+ } else if (('\r' == src[offset]) || ('\n' == src[offset])) {
+ if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) {
+ /* invalid escaped unicode sequence! */
+ state->error = json_parse_error_invalid_string_escape_sequence;
+ state->offset = offset;
+ return 1;
+ }
+
+ offset++;
+ } else {
+ /* skip character (valid part of sequence). */
+ offset++;
+ }
+ }
+
+ /* If the offset is equal to the size, we had a non-terminated string! */
+ if (offset == size) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ state->offset = offset - 1;
+ return 1;
+ }
+
+ /* skip trailing '"' or '\''. */
+ offset++;
+
+ /* add enough space to store the string. */
+ state->data_size += data_size;
+
+ /* one more byte for null terminator ending the string! */
+ state->data_size++;
+
+ /* update offset. */
+ state->offset = offset;
+
+ return 0;
+}
+
+json_weak int is_valid_unquoted_key_char(const char c);
+int is_valid_unquoted_key_char(const char c) {
+ return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') ||
+ ('A' <= c && c <= 'Z') || ('_' == c));
+}
+
+json_weak int json_get_key_size(struct json_parse_state_s *state);
+int json_get_key_size(struct json_parse_state_s *state) {
+ const size_t flags_bitset = state->flags_bitset;
+
+ if (json_parse_flags_allow_unquoted_keys & flags_bitset) {
+ size_t offset = state->offset;
+ const size_t size = state->size;
+ const char *const src = state->src;
+ size_t data_size = state->data_size;
+
+ /* if we are allowing unquoted keys, first grok for a quote... */
+ if ('"' == src[offset]) {
+ /* ... if we got a comma, just parse the key as a string as normal. */
+ return json_get_string_size(state, 1);
+ } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) &&
+ ('\'' == src[offset])) {
+ /* ... if we got a comma, just parse the key as a string as normal. */
+ return json_get_string_size(state, 1);
+ } else {
+ while ((offset < size) && is_valid_unquoted_key_char(src[offset])) {
+ offset++;
+ data_size++;
+ }
+
+ /* one more byte for null terminator ending the string! */
+ data_size++;
+
+ if (json_parse_flags_allow_location_information & flags_bitset) {
+ state->dom_size += sizeof(struct json_string_ex_s);
+ } else {
+ state->dom_size += sizeof(struct json_string_s);
+ }
+
+ /* update offset. */
+ state->offset = offset;
+
+ /* update data_size. */
+ state->data_size = data_size;
+
+ return 0;
+ }
+ } else {
+ /* we are only allowed to have quoted keys, so just parse a string! */
+ return json_get_string_size(state, 1);
+ }
+}
+
+json_weak int json_get_object_size(struct json_parse_state_s *state,
+ int is_global_object);
+int json_get_object_size(struct json_parse_state_s *state,
+ int is_global_object) {
+ const size_t flags_bitset = state->flags_bitset;
+ const char *const src = state->src;
+ const size_t size = state->size;
+ size_t elements = 0;
+ int allow_comma = 0;
+ int found_closing_brace = 0;
+
+ if (is_global_object) {
+ /* if we found an opening '{' of an object, we actually have a normal JSON
+ * object at the root of the DOM... */
+ if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) {
+ /* . and we don't actually have a global object after all! */
+ is_global_object = 0;
+ }
+ }
+
+ if (!is_global_object) {
+ if ('{' != src[state->offset]) {
+ state->error = json_parse_error_unknown;
+ return 1;
+ }
+
+ /* skip leading '{'. */
+ state->offset++;
+ }
+
+ state->dom_size += sizeof(struct json_object_s);
+
+ if ((state->offset == size) && !is_global_object) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ do {
+ if (!is_global_object) {
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ if ('}' == src[state->offset]) {
+ /* skip trailing '}'. */
+ state->offset++;
+
+ found_closing_brace = 1;
+
+ /* finished the object! */
+ break;
+ }
+ } else {
+ /* we don't require brackets, so that means the object ends when the input
+ * stream ends! */
+ if (json_skip_all_skippables(state)) {
+ break;
+ }
+ }
+
+ /* if we parsed at least one element previously, grok for a comma. */
+ if (allow_comma) {
+ if (',' == src[state->offset]) {
+ /* skip comma. */
+ state->offset++;
+ allow_comma = 0;
+ } else if (json_parse_flags_allow_no_commas & flags_bitset) {
+ /* we don't require a comma, and we didn't find one, which is ok! */
+ allow_comma = 0;
+ } else {
+ /* otherwise we are required to have a comma, and we found none. */
+ state->error = json_parse_error_expected_comma_or_closing_bracket;
+ return 1;
+ }
+
+ if (json_parse_flags_allow_trailing_comma & flags_bitset) {
+ continue;
+ } else {
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+ }
+ }
+
+ if (json_get_key_size(state)) {
+ /* key parsing failed! */
+ state->error = json_parse_error_invalid_string;
+ return 1;
+ }
+
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ if (json_parse_flags_allow_equals_in_object & flags_bitset) {
+ const char current = src[state->offset];
+ if ((':' != current) && ('=' != current)) {
+ state->error = json_parse_error_expected_colon;
+ return 1;
+ }
+ } else {
+ if (':' != src[state->offset]) {
+ state->error = json_parse_error_expected_colon;
+ return 1;
+ }
+ }
+
+ /* skip colon. */
+ state->offset++;
+
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ if (json_get_value_size(state, /* is_global_object = */ 0)) {
+ /* value parsing failed! */
+ return 1;
+ }
+
+ /* successfully parsed a name/value pair! */
+ elements++;
+ allow_comma = 1;
+ } while (state->offset < size);
+
+ if ((state->offset == size) && !is_global_object && !found_closing_brace) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ state->dom_size += sizeof(struct json_object_element_s) * elements;
+
+ return 0;
+}
+
+json_weak int json_get_array_size(struct json_parse_state_s *state);
+int json_get_array_size(struct json_parse_state_s *state) {
+ const size_t flags_bitset = state->flags_bitset;
+ size_t elements = 0;
+ int allow_comma = 0;
+ const char *const src = state->src;
+ const size_t size = state->size;
+
+ if ('[' != src[state->offset]) {
+ /* expected array to begin with leading '['. */
+ state->error = json_parse_error_unknown;
+ return 1;
+ }
+
+ /* skip leading '['. */
+ state->offset++;
+
+ state->dom_size += sizeof(struct json_array_s);
+
+ while (state->offset < size) {
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ if (']' == src[state->offset]) {
+ /* skip trailing ']'. */
+ state->offset++;
+
+ state->dom_size += sizeof(struct json_array_element_s) * elements;
+
+ /* finished the object! */
+ return 0;
+ }
+
+ /* if we parsed at least once element previously, grok for a comma. */
+ if (allow_comma) {
+ if (',' == src[state->offset]) {
+ /* skip comma. */
+ state->offset++;
+ allow_comma = 0;
+ } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) {
+ state->error = json_parse_error_expected_comma_or_closing_bracket;
+ return 1;
+ }
+
+ if (json_parse_flags_allow_trailing_comma & flags_bitset) {
+ allow_comma = 0;
+ continue;
+ } else {
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+ }
+ }
+
+ if (json_get_value_size(state, /* is_global_object = */ 0)) {
+ /* value parsing failed! */
+ return 1;
+ }
+
+ /* successfully parsed an array element! */
+ elements++;
+ allow_comma = 1;
+ }
+
+ /* we consumed the entire input before finding the closing ']' of the array!
+ */
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+}
+
+json_weak int json_get_number_size(struct json_parse_state_s *state);
+int json_get_number_size(struct json_parse_state_s *state) {
+ const size_t flags_bitset = state->flags_bitset;
+ size_t offset = state->offset;
+ const size_t size = state->size;
+ int had_leading_digits = 0;
+ const char *const src = state->src;
+
+ state->dom_size += sizeof(struct json_number_s);
+
+ if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) &&
+ (offset + 1 < size) && ('0' == src[offset]) &&
+ (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) {
+ /* skip the leading 0x that identifies a hexadecimal number. */
+ offset += 2;
+
+ /* consume hexadecimal digits. */
+ while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') ||
+ ('a' <= src[offset] && src[offset] <= 'f') ||
+ ('A' <= src[offset] && src[offset] <= 'F'))) {
+ offset++;
+ }
+ } else {
+ int found_sign = 0;
+ int inf_or_nan = 0;
+
+ if ((offset < size) &&
+ (('-' == src[offset]) ||
+ ((json_parse_flags_allow_leading_plus_sign & flags_bitset) &&
+ ('+' == src[offset])))) {
+ /* skip valid leading '-' or '+'. */
+ offset++;
+
+ found_sign = 1;
+ }
+
+ if (json_parse_flags_allow_inf_and_nan & flags_bitset) {
+ const char inf[9] = "Infinity";
+ const size_t inf_strlen = sizeof(inf) - 1;
+ const char nan[4] = "NaN";
+ const size_t nan_strlen = sizeof(nan) - 1;
+
+ if (offset + inf_strlen < size) {
+ int found = 1;
+ size_t i;
+ for (i = 0; i < inf_strlen; i++) {
+ if (inf[i] != src[offset + i]) {
+ found = 0;
+ break;
+ }
+ }
+
+ if (found) {
+ /* We found our special 'Infinity' keyword! */
+ offset += inf_strlen;
+
+ inf_or_nan = 1;
+ }
+ }
+
+ if (offset + nan_strlen < size) {
+ int found = 1;
+ size_t i;
+ for (i = 0; i < nan_strlen; i++) {
+ if (nan[i] != src[offset + i]) {
+ found = 0;
+ break;
+ }
+ }
+
+ if (found) {
+ /* We found our special 'NaN' keyword! */
+ offset += nan_strlen;
+
+ inf_or_nan = 1;
+ }
+ }
+
+ if (inf_or_nan) {
+ if (offset < size) {
+ switch (src[offset]) {
+ default:
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'e':
+ case 'E':
+ /* cannot follow an inf or nan with digits! */
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+ }
+ }
+ }
+
+ if (found_sign && !inf_or_nan && (offset < size) &&
+ !('0' <= src[offset] && src[offset] <= '9')) {
+ /* check if we are allowing leading '.'. */
+ if (!(json_parse_flags_allow_leading_or_trailing_decimal_point &
+ flags_bitset) ||
+ ('.' != src[offset])) {
+ /* a leading '-' must be immediately followed by any digit! */
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+ }
+
+ if ((offset < size) && ('0' == src[offset])) {
+ /* skip valid '0'. */
+ offset++;
+
+ /* we need to record whether we had any leading digits for checks later.
+ */
+ had_leading_digits = 1;
+
+ if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) {
+ /* a leading '0' must not be immediately followed by any digit! */
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+ }
+
+ /* the main digits of our number next. */
+ while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) {
+ offset++;
+
+ /* we need to record whether we had any leading digits for checks later.
+ */
+ had_leading_digits = 1;
+ }
+
+ if ((offset < size) && ('.' == src[offset])) {
+ offset++;
+
+ if ((offset >= size) || !('0' <= src[offset] && src[offset] <= '9')) {
+ if (!(json_parse_flags_allow_leading_or_trailing_decimal_point &
+ flags_bitset) ||
+ !had_leading_digits) {
+ /* a decimal point must be followed by at least one digit. */
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+ }
+
+ /* a decimal point can be followed by more digits of course! */
+ while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) {
+ offset++;
+ }
+ }
+
+ if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) {
+ /* our number has an exponent! Skip 'e' or 'E'. */
+ offset++;
+
+ if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) {
+ /* skip optional '-' or '+'. */
+ offset++;
+ }
+
+ if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) {
+ /* an exponent must have at least one digit! */
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+
+ /* consume exponent digits. */
+ do {
+ offset++;
+ } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9'));
+ }
+ }
+
+ if (offset < size) {
+ switch (src[offset]) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ case '}':
+ case ',':
+ case ']':
+ /* all of the above are ok. */
+ break;
+ case '=':
+ if (json_parse_flags_allow_equals_in_object & flags_bitset) {
+ break;
+ }
+
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ default:
+ state->error = json_parse_error_invalid_number_format;
+ state->offset = offset;
+ return 1;
+ }
+ }
+
+ state->data_size += offset - state->offset;
+
+ /* one more byte for null terminator ending the number string! */
+ state->data_size++;
+
+ /* update offset. */
+ state->offset = offset;
+
+ return 0;
+}
+
+json_weak int json_get_value_size(struct json_parse_state_s *state,
+ int is_global_object);
+int json_get_value_size(struct json_parse_state_s *state,
+ int is_global_object) {
+ const size_t flags_bitset = state->flags_bitset;
+ const char *const src = state->src;
+ size_t offset;
+ const size_t size = state->size;
+
+ if (json_parse_flags_allow_location_information & flags_bitset) {
+ state->dom_size += sizeof(struct json_value_ex_s);
+ } else {
+ state->dom_size += sizeof(struct json_value_s);
+ }
+
+ if (is_global_object) {
+ return json_get_object_size(state, /* is_global_object = */ 1);
+ } else {
+ if (json_skip_all_skippables(state)) {
+ state->error = json_parse_error_premature_end_of_buffer;
+ return 1;
+ }
+
+ /* can cache offset now. */
+ offset = state->offset;
+
+ switch (src[offset]) {
+ case '"':
+ return json_get_string_size(state, 0);
+ case '\'':
+ if (json_parse_flags_allow_single_quoted_strings & flags_bitset) {
+ return json_get_string_size(state, 0);
+ } else {
+ /* invalid value! */
+ state->error = json_parse_error_invalid_value;
+ return 1;
+ }
+ case '{':
+ return json_get_object_size(state, /* is_global_object = */ 0);
+ case '[':
+ return json_get_array_size(state);
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return json_get_number_size(state);
+ case '+':
+ if (json_parse_flags_allow_leading_plus_sign & flags_bitset) {
+ return json_get_number_size(state);
+ } else {
+ /* invalid value! */
+ state->error = json_parse_error_invalid_number_format;
+ return 1;
+ }
+ case '.':
+ if (json_parse_flags_allow_leading_or_trailing_decimal_point &
+ flags_bitset) {
+ return json_get_number_size(state);
+ } else {
+ /* invalid value! */
+ state->error = json_parse_error_invalid_number_format;
+ return 1;
+ }
+ default:
+ if ((offset + 4) <= size && 't' == src[offset + 0] &&
+ 'r' == src[offset + 1] && 'u' == src[offset + 2] &&
+ 'e' == src[offset + 3]) {
+ state->offset += 4;
+ return 0;
+ } else if ((offset + 5) <= size && 'f' == src[offset + 0] &&
+ 'a' == src[offset + 1] && 'l' == src[offset + 2] &&
+ 's' == src[offset + 3] && 'e' == src[offset + 4]) {
+ state->offset += 5;
+ return 0;
+ } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] &&
+ 'u' == state->src[offset + 1] &&
+ 'l' == state->src[offset + 2] &&
+ 'l' == state->src[offset + 3]) {
+ state->offset += 4;
+ return 0;
+ } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) &&
+ (offset + 3) <= size && 'N' == src[offset + 0] &&
+ 'a' == src[offset + 1] && 'N' == src[offset + 2]) {
+ return json_get_number_size(state);
+ } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) &&
+ (offset + 8) <= size && 'I' == src[offset + 0] &&
+ 'n' == src[offset + 1] && 'f' == src[offset + 2] &&
+ 'i' == src[offset + 3] && 'n' == src[offset + 4] &&
+ 'i' == src[offset + 5] && 't' == src[offset + 6] &&
+ 'y' == src[offset + 7]) {
+ return json_get_number_size(state);
+ }
+
+ /* invalid value! */
+ state->error = json_parse_error_invalid_value;
+ return 1;
+ }
+ }
+}
+
+json_weak void json_parse_value(struct json_parse_state_s *state,
+ int is_global_object,
+ struct json_value_s *value);
+
+json_weak void json_parse_string(struct json_parse_state_s *state,
+ struct json_string_s *string);
+void json_parse_string(struct json_parse_state_s *state,
+ struct json_string_s *string) {
+ size_t offset = state->offset;
+ size_t bytes_written = 0;
+ const char *const src = state->src;
+ const char quote_to_use = '\'' == src[offset] ? '\'' : '"';
+ char *data = state->data;
+ unsigned long high_surrogate = 0;
+ unsigned long codepoint;
+
+ string->string = data;
+
+ /* skip leading '"' or '\''. */
+ offset++;
+
+ while (quote_to_use != src[offset]) {
+ if ('\\' == src[offset]) {
+ /* skip the reverse solidus. */
+ offset++;
+
+ switch (src[offset++]) {
+ default:
+ return; /* we cannot ever reach here. */
+ case 'u': {
+ codepoint = 0;
+ if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) {
+ return; /* this shouldn't happen as the value was already validated.
+ */
+ }
+
+ offset += 4;
+
+ if (codepoint <= 0x7fu) {
+ data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */
+ } else if (codepoint <= 0x7ffu) {
+ data[bytes_written++] =
+ (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */
+ data[bytes_written++] =
+ (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */
+ } else if (codepoint >= 0xd800 &&
+ codepoint <= 0xdbff) { /* high surrogate. */
+ high_surrogate = codepoint;
+ continue; /* we need the low half to form a complete codepoint. */
+ } else if (codepoint >= 0xdc00 &&
+ codepoint <= 0xdfff) { /* low surrogate. */
+ /* combine with the previously read half to obtain the complete
+ * codepoint. */
+ const unsigned long surrogate_offset =
+ 0x10000u - (0xD800u << 10) - 0xDC00u;
+ codepoint = (high_surrogate << 10) + codepoint + surrogate_offset;
+ high_surrogate = 0;
+ data[bytes_written++] =
+ (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */
+ data[bytes_written++] =
+ (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */
+ data[bytes_written++] =
+ (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */
+ data[bytes_written++] =
+ (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */
+ } else {
+ /* we assume the value was validated and thus is within the valid
+ * range. */
+ data[bytes_written++] =
+ (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */
+ data[bytes_written++] =
+ (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */
+ data[bytes_written++] =
+ (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */
+ }
+ } break;
+ case '"':
+ data[bytes_written++] = '"';
+ break;
+ case '\\':
+ data[bytes_written++] = '\\';
+ break;
+ case '/':
+ data[bytes_written++] = '/';
+ break;
+ case 'b':
+ data[bytes_written++] = '\b';
+ break;
+ case 'f':
+ data[bytes_written++] = '\f';
+ break;
+ case 'n':
+ data[bytes_written++] = '\n';
+ break;
+ case 'r':
+ data[bytes_written++] = '\r';
+ break;
+ case 't':
+ data[bytes_written++] = '\t';
+ break;
+ case '\r':
+ data[bytes_written++] = '\r';
+
+ /* check if we have a "\r\n" sequence. */
+ if ('\n' == src[offset]) {
+ data[bytes_written++] = '\n';
+ offset++;
+ }
+
+ break;
+ case '\n':
+ data[bytes_written++] = '\n';
+ break;
+ }
+ } else {
+ /* copy the character. */
+ data[bytes_written++] = src[offset++];
+ }
+ }
+
+ /* skip trailing '"' or '\''. */
+ offset++;
+
+ /* record the size of the string. */
+ string->string_size = bytes_written;
+
+ /* add null terminator to string. */
+ data[bytes_written++] = '\0';
+
+ /* move data along. */
+ state->data += bytes_written;
+
+ /* update offset. */
+ state->offset = offset;
+}
+
+json_weak void json_parse_key(struct json_parse_state_s *state,
+ struct json_string_s *string);
+void json_parse_key(struct json_parse_state_s *state,
+ struct json_string_s *string) {
+ if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) {
+ const char *const src = state->src;
+ char *const data = state->data;
+ size_t offset = state->offset;
+
+ /* if we are allowing unquoted keys, check for quoted anyway... */
+ if (('"' == src[offset]) || ('\'' == src[offset])) {
+ /* ... if we got a quote, just parse the key as a string as normal. */
+ json_parse_string(state, string);
+ } else {
+ size_t size = 0;
+
+ string->string = state->data;
+
+ while (is_valid_unquoted_key_char(src[offset])) {
+ data[size++] = src[offset++];
+ }
+
+ /* add null terminator to string. */
+ data[size] = '\0';
+
+ /* record the size of the string. */
+ string->string_size = size++;
+
+ /* move data along. */
+ state->data += size;
+
+ /* update offset. */
+ state->offset = offset;
+ }
+ } else {
+ /* we are only allowed to have quoted keys, so just parse a string! */
+ json_parse_string(state, string);
+ }
+}
+
+json_weak void json_parse_object(struct json_parse_state_s *state,
+ int is_global_object,
+ struct json_object_s *object);
+void json_parse_object(struct json_parse_state_s *state, int is_global_object,
+ struct json_object_s *object) {
+ const size_t flags_bitset = state->flags_bitset;
+ const size_t size = state->size;
+ const char *const src = state->src;
+ size_t elements = 0;
+ int allow_comma = 0;
+ struct json_object_element_s *previous = json_null;
+
+ if (is_global_object) {
+ /* if we skipped some whitespace, and then found an opening '{' of an. */
+ /* object, we actually have a normal JSON object at the root of the DOM...
+ */
+ if ('{' == src[state->offset]) {
+ /* . and we don't actually have a global object after all! */
+ is_global_object = 0;
+ }
+ }
+
+ if (!is_global_object) {
+ /* skip leading '{'. */
+ state->offset++;
+ }
+
+ (void)json_skip_all_skippables(state);
+
+ /* reset elements. */
+ elements = 0;
+
+ while (state->offset < size) {
+ struct json_object_element_s *element = json_null;
+ struct json_string_s *string = json_null;
+ struct json_value_s *value = json_null;
+
+ if (!is_global_object) {
+ (void)json_skip_all_skippables(state);
+
+ if ('}' == src[state->offset]) {
+ /* skip trailing '}'. */
+ state->offset++;
+
+ /* finished the object! */
+ break;
+ }
+ } else {
+ if (json_skip_all_skippables(state)) {
+ /* global object ends when the file ends! */
+ break;
+ }
+ }
+
+ /* if we parsed at least one element previously, grok for a comma. */
+ if (allow_comma) {
+ if (',' == src[state->offset]) {
+ /* skip comma. */
+ state->offset++;
+ allow_comma = 0;
+ continue;
+ }
+ }
+
+ element = (struct json_object_element_s *)state->dom;
+
+ state->dom += sizeof(struct json_object_element_s);
+
+ if (json_null == previous) {
+ /* this is our first element, so record it in our object. */
+ object->start = element;
+ } else {
+ previous->next = element;
+ }
+
+ previous = element;
+
+ if (json_parse_flags_allow_location_information & flags_bitset) {
+ struct json_string_ex_s *string_ex =
+ (struct json_string_ex_s *)state->dom;
+ state->dom += sizeof(struct json_string_ex_s);
+
+ string_ex->offset = state->offset;
+ string_ex->line_no = state->line_no;
+ string_ex->row_no = state->offset - state->line_offset;
+
+ string = &(string_ex->string);
+ } else {
+ string = (struct json_string_s *)state->dom;
+ state->dom += sizeof(struct json_string_s);
+ }
+
+ element->name = string;
+
+ (void)json_parse_key(state, string);
+
+ (void)json_skip_all_skippables(state);
+
+ /* skip colon or equals. */
+ state->offset++;
+
+ (void)json_skip_all_skippables(state);
+
+ if (json_parse_flags_allow_location_information & flags_bitset) {
+ struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom;
+ state->dom += sizeof(struct json_value_ex_s);
+
+ value_ex->offset = state->offset;
+ value_ex->line_no = state->line_no;
+ value_ex->row_no = state->offset - state->line_offset;
+
+ value = &(value_ex->value);
+ } else {
+ value = (struct json_value_s *)state->dom;
+ state->dom += sizeof(struct json_value_s);
+ }
+
+ element->value = value;
+
+ json_parse_value(state, /* is_global_object = */ 0, value);
+
+ /* successfully parsed a name/value pair! */
+ elements++;
+ allow_comma = 1;
+ }
+
+ /* if we had at least one element, end the linked list. */
+ if (previous) {
+ previous->next = json_null;
+ }
+
+ if (0 == elements) {
+ object->start = json_null;
+ }
+
+ object->length = elements;
+}
+
+json_weak void json_parse_array(struct json_parse_state_s *state,
+ struct json_array_s *array);
+void json_parse_array(struct json_parse_state_s *state,
+ struct json_array_s *array) {
+ const char *const src = state->src;
+ const size_t size = state->size;
+ size_t elements = 0;
+ int allow_comma = 0;
+ struct json_array_element_s *previous = json_null;
+
+ /* skip leading '['. */
+ state->offset++;
+
+ (void)json_skip_all_skippables(state);
+
+ /* reset elements. */
+ elements = 0;
+
+ do {
+ struct json_array_element_s *element = json_null;
+ struct json_value_s *value = json_null;
+
+ (void)json_skip_all_skippables(state);
+
+ if (']' == src[state->offset]) {
+ /* skip trailing ']'. */
+ state->offset++;
+
+ /* finished the array! */
+ break;
+ }
+
+ /* if we parsed at least one element previously, grok for a comma. */
+ if (allow_comma) {
+ if (',' == src[state->offset]) {
+ /* skip comma. */
+ state->offset++;
+ allow_comma = 0;
+ continue;
+ }
+ }
+
+ element = (struct json_array_element_s *)state->dom;
+
+ state->dom += sizeof(struct json_array_element_s);
+
+ if (json_null == previous) {
+ /* this is our first element, so record it in our array. */
+ array->start = element;
+ } else {
+ previous->next = element;
+ }
+
+ previous = element;
+
+ if (json_parse_flags_allow_location_information & state->flags_bitset) {
+ struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom;
+ state->dom += sizeof(struct json_value_ex_s);
+
+ value_ex->offset = state->offset;
+ value_ex->line_no = state->line_no;
+ value_ex->row_no = state->offset - state->line_offset;
+
+ value = &(value_ex->value);
+ } else {
+ value = (struct json_value_s *)state->dom;
+ state->dom += sizeof(struct json_value_s);
+ }
+
+ element->value = value;
+
+ json_parse_value(state, /* is_global_object = */ 0, value);
+
+ /* successfully parsed an array element! */
+ elements++;
+ allow_comma = 1;
+ } while (state->offset < size);
+
+ /* end the linked list. */
+ if (previous) {
+ previous->next = json_null;
+ }
+
+ if (0 == elements) {
+ array->start = json_null;
+ }
+
+ array->length = elements;
+}
+
+json_weak void json_parse_number(struct json_parse_state_s *state,
+ struct json_number_s *number);
+void json_parse_number(struct json_parse_state_s *state,
+ struct json_number_s *number) {
+ const size_t flags_bitset = state->flags_bitset;
+ size_t offset = state->offset;
+ const size_t size = state->size;
+ size_t bytes_written = 0;
+ const char *const src = state->src;
+ char *data = state->data;
+
+ number->number = data;
+
+ if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) {
+ if (('0' == src[offset]) &&
+ (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) {
+ /* consume hexadecimal digits. */
+ while ((offset < size) &&
+ (('0' <= src[offset] && src[offset] <= '9') ||
+ ('a' <= src[offset] && src[offset] <= 'f') ||
+ ('A' <= src[offset] && src[offset] <= 'F') ||
+ ('x' == src[offset]) || ('X' == src[offset]))) {
+ data[bytes_written++] = src[offset++];
+ }
+ }
+ }
+
+ while (offset < size) {
+ int end = 0;
+
+ switch (src[offset]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case 'e':
+ case 'E':
+ case '+':
+ case '-':
+ data[bytes_written++] = src[offset++];
+ break;
+ default:
+ end = 1;
+ break;
+ }
+
+ if (0 != end) {
+ break;
+ }
+ }
+
+ if (json_parse_flags_allow_inf_and_nan & flags_bitset) {
+ const size_t inf_strlen = 8; /* = strlen("Infinity");. */
+ const size_t nan_strlen = 3; /* = strlen("NaN");. */
+
+ if (offset + inf_strlen < size) {
+ if ('I' == src[offset]) {
+ size_t i;
+ /* We found our special 'Infinity' keyword! */
+ for (i = 0; i < inf_strlen; i++) {
+ data[bytes_written++] = src[offset++];
+ }
+ }
+ }
+
+ if (offset + nan_strlen < size) {
+ if ('N' == src[offset]) {
+ size_t i;
+ /* We found our special 'NaN' keyword! */
+ for (i = 0; i < nan_strlen; i++) {
+ data[bytes_written++] = src[offset++];
+ }
+ }
+ }
+ }
+
+ /* record the size of the number. */
+ number->number_size = bytes_written;
+ /* add null terminator to number string. */
+ data[bytes_written++] = '\0';
+ /* move data along. */
+ state->data += bytes_written;
+ /* update offset. */
+ state->offset = offset;
+}
+
+json_weak void json_parse_value(struct json_parse_state_s *state,
+ int is_global_object,
+ struct json_value_s *value);
+void json_parse_value(struct json_parse_state_s *state, int is_global_object,
+ struct json_value_s *value) {
+ const size_t flags_bitset = state->flags_bitset;
+ const char *const src = state->src;
+ const size_t size = state->size;
+ size_t offset;
+
+ (void)json_skip_all_skippables(state);
+
+ /* cache offset now. */
+ offset = state->offset;
+
+ if (is_global_object) {
+ value->type = json_type_object;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_object_s);
+ json_parse_object(state, /* is_global_object = */ 1,
+ (struct json_object_s *)value->payload);
+ } else {
+ switch (src[offset]) {
+ case '"':
+ case '\'':
+ value->type = json_type_string;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_string_s);
+ json_parse_string(state, (struct json_string_s *)value->payload);
+ break;
+ case '{':
+ value->type = json_type_object;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_object_s);
+ json_parse_object(state, /* is_global_object = */ 0,
+ (struct json_object_s *)value->payload);
+ break;
+ case '[':
+ value->type = json_type_array;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_array_s);
+ json_parse_array(state, (struct json_array_s *)value->payload);
+ break;
+ case '-':
+ case '+':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ value->type = json_type_number;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_number_s);
+ json_parse_number(state, (struct json_number_s *)value->payload);
+ break;
+ default:
+ if ((offset + 4) <= size && 't' == src[offset + 0] &&
+ 'r' == src[offset + 1] && 'u' == src[offset + 2] &&
+ 'e' == src[offset + 3]) {
+ value->type = json_type_true;
+ value->payload = json_null;
+ state->offset += 4;
+ } else if ((offset + 5) <= size && 'f' == src[offset + 0] &&
+ 'a' == src[offset + 1] && 'l' == src[offset + 2] &&
+ 's' == src[offset + 3] && 'e' == src[offset + 4]) {
+ value->type = json_type_false;
+ value->payload = json_null;
+ state->offset += 5;
+ } else if ((offset + 4) <= size && 'n' == src[offset + 0] &&
+ 'u' == src[offset + 1] && 'l' == src[offset + 2] &&
+ 'l' == src[offset + 3]) {
+ value->type = json_type_null;
+ value->payload = json_null;
+ state->offset += 4;
+ } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) &&
+ (offset + 3) <= size && 'N' == src[offset + 0] &&
+ 'a' == src[offset + 1] && 'N' == src[offset + 2]) {
+ value->type = json_type_number;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_number_s);
+ json_parse_number(state, (struct json_number_s *)value->payload);
+ } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) &&
+ (offset + 8) <= size && 'I' == src[offset + 0] &&
+ 'n' == src[offset + 1] && 'f' == src[offset + 2] &&
+ 'i' == src[offset + 3] && 'n' == src[offset + 4] &&
+ 'i' == src[offset + 5] && 't' == src[offset + 6] &&
+ 'y' == src[offset + 7]) {
+ value->type = json_type_number;
+ value->payload = state->dom;
+ state->dom += sizeof(struct json_number_s);
+ json_parse_number(state, (struct json_number_s *)value->payload);
+ }
+ break;
+ }
+ }
+}
+
+struct json_value_s *
+json_parse_ex(const void *src, size_t src_size, size_t flags_bitset,
+ void *(*alloc_func_ptr)(void *user_data, size_t size),
+ void *user_data, struct json_parse_result_s *result) {
+ struct json_parse_state_s state;
+ void *allocation;
+ struct json_value_s *value;
+ size_t total_size;
+ int input_error;
+
+ if (result) {
+ result->error = json_parse_error_none;
+ result->error_offset = 0;
+ result->error_line_no = 0;
+ result->error_row_no = 0;
+ }
+
+ if (json_null == src) {
+ /* invalid src pointer was null! */
+ return json_null;
+ }
+
+ state.src = (const char *)src;
+ state.size = src_size;
+ state.offset = 0;
+ state.line_no = 1;
+ state.line_offset = 0;
+ state.error = json_parse_error_none;
+ state.dom_size = 0;
+ state.data_size = 0;
+ state.flags_bitset = flags_bitset;
+
+ input_error = json_get_value_size(
+ &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset));
+
+ if (0 == input_error) {
+ json_skip_all_skippables(&state);
+
+ if (state.offset != state.size) {
+ /* our parsing didn't have an error, but there are characters remaining in
+ * the input that weren't part of the JSON! */
+
+ state.error = json_parse_error_unexpected_trailing_characters;
+ input_error = 1;
+ }
+ }
+
+ if (input_error) {
+ /* parsing value's size failed (most likely an invalid JSON DOM!). */
+ if (result) {
+ result->error = state.error;
+ result->error_offset = state.offset;
+ result->error_line_no = state.line_no;
+ result->error_row_no = state.offset - state.line_offset;
+ }
+ return json_null;
+ }
+
+ /* our total allocation is the combination of the dom and data sizes (we. */
+ /* first encode the structure of the JSON, and then the data referenced by. */
+ /* the JSON values). */
+ total_size = state.dom_size + state.data_size;
+
+ if (json_null == alloc_func_ptr) {
+ allocation = SHEREDOM_JSON_H_malloc(total_size);
+ } else {
+ allocation = alloc_func_ptr(user_data, total_size);
+ }
+
+ if (json_null == allocation) {
+ /* malloc failed! */
+ if (result) {
+ result->error = json_parse_error_allocator_failed;
+ result->error_offset = 0;
+ result->error_line_no = 0;
+ result->error_row_no = 0;
+ }
+
+ return json_null;
+ }
+
+ /* reset offset so we can reuse it. */
+ state.offset = 0;
+
+ /* reset the line information so we can reuse it. */
+ state.line_no = 1;
+ state.line_offset = 0;
+
+ state.dom = (char *)allocation;
+ state.data = state.dom + state.dom_size;
+
+ if (json_parse_flags_allow_location_information & state.flags_bitset) {
+ struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom;
+ state.dom += sizeof(struct json_value_ex_s);
+
+ value_ex->offset = state.offset;
+ value_ex->line_no = state.line_no;
+ value_ex->row_no = state.offset - state.line_offset;
+
+ value = &(value_ex->value);
+ } else {
+ value = (struct json_value_s *)state.dom;
+ state.dom += sizeof(struct json_value_s);
+ }
+
+ json_parse_value(
+ &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset),
+ value);
+
+ return (struct json_value_s *)allocation;
+}
+
+struct json_value_s *json_parse(const void *src, size_t src_size) {
+ return json_parse_ex(src, src_size, json_parse_flags_default, json_null,
+ json_null, json_null);
+}
+
+struct json_extract_result_s {
+ size_t dom_size;
+ size_t data_size;
+};
+
+struct json_value_s *json_extract_value(const struct json_value_s *value) {
+ return json_extract_value_ex(value, json_null, json_null);
+}
+
+json_weak struct json_extract_result_s
+json_extract_get_number_size(const struct json_number_s *const number);
+json_weak struct json_extract_result_s
+json_extract_get_string_size(const struct json_string_s *const string);
+json_weak struct json_extract_result_s
+json_extract_get_object_size(const struct json_object_s *const object);
+json_weak struct json_extract_result_s
+json_extract_get_array_size(const struct json_array_s *const array);
+json_weak struct json_extract_result_s
+json_extract_get_value_size(const struct json_value_s *const value);
+
+struct json_extract_result_s
+json_extract_get_number_size(const struct json_number_s *const number) {
+ struct json_extract_result_s result;
+ result.dom_size = sizeof(struct json_number_s);
+ result.data_size = number->number_size;
+ return result;
+}
+
+struct json_extract_result_s
+json_extract_get_string_size(const struct json_string_s *const string) {
+ struct json_extract_result_s result;
+ result.dom_size = sizeof(struct json_string_s);
+ result.data_size = string->string_size + 1;
+ return result;
+}
+
+struct json_extract_result_s
+json_extract_get_object_size(const struct json_object_s *const object) {
+ struct json_extract_result_s result;
+ size_t i;
+ const struct json_object_element_s *element = object->start;
+
+ result.dom_size = sizeof(struct json_object_s) +
+ (sizeof(struct json_object_element_s) * object->length);
+ result.data_size = 0;
+
+ for (i = 0; i < object->length; i++) {
+ const struct json_extract_result_s string_result =
+ json_extract_get_string_size(element->name);
+ const struct json_extract_result_s value_result =
+ json_extract_get_value_size(element->value);
+
+ result.dom_size += string_result.dom_size;
+ result.data_size += string_result.data_size;
+
+ result.dom_size += value_result.dom_size;
+ result.data_size += value_result.data_size;
+
+ element = element->next;
+ }
+
+ return result;
+}
+
+struct json_extract_result_s
+json_extract_get_array_size(const struct json_array_s *const array) {
+ struct json_extract_result_s result;
+ size_t i;
+ const struct json_array_element_s *element = array->start;
+
+ result.dom_size = sizeof(struct json_array_s) +
+ (sizeof(struct json_array_element_s) * array->length);
+ result.data_size = 0;
+
+ for (i = 0; i < array->length; i++) {
+ const struct json_extract_result_s value_result =
+ json_extract_get_value_size(element->value);
+
+ result.dom_size += value_result.dom_size;
+ result.data_size += value_result.data_size;
+
+ element = element->next;
+ }
+
+ return result;
+}
+
+struct json_extract_result_s
+json_extract_get_value_size(const struct json_value_s *const value) {
+ struct json_extract_result_s result = {0, 0};
+
+ switch (value->type) {
+ default:
+ break;
+ case json_type_object:
+ result = json_extract_get_object_size(
+ (const struct json_object_s *)value->payload);
+ break;
+ case json_type_array:
+ result = json_extract_get_array_size(
+ (const struct json_array_s *)value->payload);
+ break;
+ case json_type_number:
+ result = json_extract_get_number_size(
+ (const struct json_number_s *)value->payload);
+ break;
+ case json_type_string:
+ result = json_extract_get_string_size(
+ (const struct json_string_s *)value->payload);
+ break;
+ }
+
+ result.dom_size += sizeof(struct json_value_s);
+
+ return result;
+}
+
+struct json_extract_state_s {
+ char *dom;
+ char *data;
+};
+
+json_weak void json_extract_copy_value(struct json_extract_state_s *const state,
+ const struct json_value_s *const value);
+void json_extract_copy_value(struct json_extract_state_s *const state,
+ const struct json_value_s *const value) {
+ struct json_string_s *string;
+ struct json_number_s *number;
+ struct json_object_s *object;
+ struct json_array_s *array;
+ struct json_value_s *new_value;
+
+ memcpy(state->dom, value, sizeof(struct json_value_s));
+ new_value = (struct json_value_s *)state->dom;
+ state->dom += sizeof(struct json_value_s);
+ new_value->payload = state->dom;
+
+ if (json_type_string == value->type) {
+ memcpy(state->dom, value->payload, sizeof(struct json_string_s));
+ string = (struct json_string_s *)state->dom;
+ state->dom += sizeof(struct json_string_s);
+
+ memcpy(state->data, string->string, string->string_size + 1);
+ string->string = state->data;
+ state->data += string->string_size + 1;
+ } else if (json_type_number == value->type) {
+ memcpy(state->dom, value->payload, sizeof(struct json_number_s));
+ number = (struct json_number_s *)state->dom;
+ state->dom += sizeof(struct json_number_s);
+
+ memcpy(state->data, number->number, number->number_size);
+ number->number = state->data;
+ state->data += number->number_size;
+ } else if (json_type_object == value->type) {
+ struct json_object_element_s *element;
+ size_t i;
+
+ memcpy(state->dom, value->payload, sizeof(struct json_object_s));
+ object = (struct json_object_s *)state->dom;
+ state->dom += sizeof(struct json_object_s);
+
+ element = object->start;
+ object->start = (struct json_object_element_s *)state->dom;
+
+ for (i = 0; i < object->length; i++) {
+ struct json_value_s *previous_value;
+ struct json_object_element_s *previous_element;
+
+ memcpy(state->dom, element, sizeof(struct json_object_element_s));
+ element = (struct json_object_element_s *)state->dom;
+ state->dom += sizeof(struct json_object_element_s);
+
+ string = element->name;
+ memcpy(state->dom, string, sizeof(struct json_string_s));
+ string = (struct json_string_s *)state->dom;
+ state->dom += sizeof(struct json_string_s);
+ element->name = string;
+
+ memcpy(state->data, string->string, string->string_size + 1);
+ string->string = state->data;
+ state->data += string->string_size + 1;
+
+ previous_value = element->value;
+ element->value = (struct json_value_s *)state->dom;
+ json_extract_copy_value(state, previous_value);
+
+ previous_element = element;
+ element = element->next;
+
+ if (element) {
+ previous_element->next = (struct json_object_element_s *)state->dom;
+ }
+ }
+ } else if (json_type_array == value->type) {
+ struct json_array_element_s *element;
+ size_t i;
+
+ memcpy(state->dom, value->payload, sizeof(struct json_array_s));
+ array = (struct json_array_s *)state->dom;
+ state->dom += sizeof(struct json_array_s);
+
+ element = array->start;
+ array->start = (struct json_array_element_s *)state->dom;
+
+ for (i = 0; i < array->length; i++) {
+ struct json_value_s *previous_value;
+ struct json_array_element_s *previous_element;
+
+ memcpy(state->dom, element, sizeof(struct json_array_element_s));
+ element = (struct json_array_element_s *)state->dom;
+ state->dom += sizeof(struct json_array_element_s);
+
+ previous_value = element->value;
+ element->value = (struct json_value_s *)state->dom;
+ json_extract_copy_value(state, previous_value);
+
+ previous_element = element;
+ element = element->next;
+
+ if (element) {
+ previous_element->next = (struct json_array_element_s *)state->dom;
+ }
+ }
+ }
+}
+
+struct json_value_s *json_extract_value_ex(const struct json_value_s *value,
+ void *(*alloc_func_ptr)(void *,
+ size_t),
+ void *user_data) {
+ void *allocation;
+ struct json_extract_result_s result;
+ struct json_extract_state_s state;
+ size_t total_size;
+
+ if (json_null == value) {
+ /* invalid value was null! */
+ return json_null;
+ }
+
+ result = json_extract_get_value_size(value);
+ total_size = result.dom_size + result.data_size;
+
+ if (json_null == alloc_func_ptr) {
+ allocation = SHEREDOM_JSON_H_malloc(total_size);
+ } else {
+ allocation = alloc_func_ptr(user_data, total_size);
+ }
+
+ state.dom = (char *)allocation;
+ state.data = state.dom + result.dom_size;
+
+ json_extract_copy_value(&state, value);
+
+ return (struct json_value_s *)allocation;
+}
+
+struct json_string_s *json_value_as_string(struct json_value_s *const value) {
+ if (value->type != json_type_string) {
+ return json_null;
+ }
+
+ return (struct json_string_s *)value->payload;
+}
+
+struct json_number_s *json_value_as_number(struct json_value_s *const value) {
+ if (value->type != json_type_number) {
+ return json_null;
+ }
+
+ return (struct json_number_s *)value->payload;
+}
+
+struct json_object_s *json_value_as_object(struct json_value_s *const value) {
+ if (value->type != json_type_object) {
+ return json_null;
+ }
+
+ return (struct json_object_s *)value->payload;
+}
+
+struct json_array_s *json_value_as_array(struct json_value_s *const value) {
+ if (value->type != json_type_array) {
+ return json_null;
+ }
+
+ return (struct json_array_s *)value->payload;
+}
+
+int json_value_is_true(const struct json_value_s *const value) {
+ return value->type == json_type_true;
+}
+
+int json_value_is_false(const struct json_value_s *const value) {
+ return value->type == json_type_false;
+}
+
+int json_value_is_null(const struct json_value_s *const value) {
+ return value->type == json_type_null;
+}
+
+json_weak int
+json_write_minified_get_value_size(const struct json_value_s *value,
+ size_t *size);
+
+json_weak int json_write_get_number_size(const struct json_number_s *number,
+ size_t *size);
+int json_write_get_number_size(const struct json_number_s *number,
+ size_t *size) {
+ json_uintmax_t parsed_number;
+ size_t i;
+
+ if (number->number_size >= 2) {
+ switch (number->number[1]) {
+ default:
+ break;
+ case 'x':
+ case 'X':
+ /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal
+ * so we have to do extra work to convert it to a non-hexadecimal for JSON
+ * output. */
+ parsed_number = json_strtoumax(number->number, json_null, 0);
+
+ i = 0;
+
+ while (0 != parsed_number) {
+ parsed_number /= 10;
+ i++;
+ }
+
+ *size += i;
+ return 0;
+ }
+ }
+
+ /* check to see if the number has leading/trailing decimal point. */
+ i = 0;
+
+ /* skip any leading '+' or '-'. */
+ if ((i < number->number_size) &&
+ (('+' == number->number[i]) || ('-' == number->number[i]))) {
+ i++;
+ }
+
+ /* check if we have infinity. */
+ if ((i < number->number_size) && ('I' == number->number[i])) {
+ const char *inf = "Infinity";
+ size_t k;
+
+ for (k = i; k < number->number_size; k++) {
+ const char c = *inf++;
+
+ /* Check if we found the Infinity string! */
+ if ('\0' == c) {
+ break;
+ } else if (c != number->number[k]) {
+ break;
+ }
+ }
+
+ if ('\0' == *inf) {
+ /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */
+ *size += 22;
+
+ /* if we had a leading '-' we need to record it in the JSON output. */
+ if ('-' == number->number[0]) {
+ *size += 1;
+ }
+ }
+
+ return 0;
+ }
+
+ /* check if we have nan. */
+ if ((i < number->number_size) && ('N' == number->number[i])) {
+ const char *nan = "NaN";
+ size_t k;
+
+ for (k = i; k < number->number_size; k++) {
+ const char c = *nan++;
+
+ /* Check if we found the NaN string! */
+ if ('\0' == c) {
+ break;
+ } else if (c != number->number[k]) {
+ break;
+ }
+ }
+
+ if ('\0' == *nan) {
+ /* NaN becomes 1 because JSON can't support it. */
+ *size += 1;
+
+ return 0;
+ }
+ }
+
+ /* if we had a leading decimal point. */
+ if ((i < number->number_size) && ('.' == number->number[i])) {
+ /* 1 + because we had a leading decimal point. */
+ *size += 1;
+ goto cleanup;
+ }
+
+ for (; i < number->number_size; i++) {
+ const char c = number->number[i];
+ if (!('0' <= c && c <= '9')) {
+ break;
+ }
+ }
+
+ /* if we had a trailing decimal point. */
+ if ((i + 1 == number->number_size) && ('.' == number->number[i])) {
+ /* 1 + because we had a trailing decimal point. */
+ *size += 1;
+ goto cleanup;
+ }
+
+cleanup:
+ *size += number->number_size; /* the actual string of the number. */
+
+ /* if we had a leading '+' we don't record it in the JSON output. */
+ if ('+' == number->number[0]) {
+ *size -= 1;
+ }
+
+ return 0;
+}
+
+json_weak int json_write_get_string_size(const struct json_string_s *string,
+ size_t *size);
+int json_write_get_string_size(const struct json_string_s *string,
+ size_t *size) {
+ size_t i;
+ for (i = 0; i < string->string_size; i++) {
+ switch (string->string[i]) {
+ case '"':
+ case '\\':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ *size += 2;
+ break;
+ default:
+ *size += 1;
+ break;
+ }
+ }
+
+ *size += 2; /* need to encode the surrounding '"' characters. */
+
+ return 0;
+}
+
+json_weak int
+json_write_minified_get_array_size(const struct json_array_s *array,
+ size_t *size);
+int json_write_minified_get_array_size(const struct json_array_s *array,
+ size_t *size) {
+ struct json_array_element_s *element;
+
+ *size += 2; /* '[' and ']'. */
+
+ if (1 < array->length) {
+ *size += array->length - 1; /* ','s seperate each element. */
+ }
+
+ for (element = array->start; json_null != element; element = element->next) {
+ if (json_write_minified_get_value_size(element->value, size)) {
+ /* value was malformed! */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+json_weak int
+json_write_minified_get_object_size(const struct json_object_s *object,
+ size_t *size);
+int json_write_minified_get_object_size(const struct json_object_s *object,
+ size_t *size) {
+ struct json_object_element_s *element;
+
+ *size += 2; /* '{' and '}'. */
+
+ *size += object->length; /* ':'s seperate each name/value pair. */
+
+ if (1 < object->length) {
+ *size += object->length - 1; /* ','s seperate each element. */
+ }
+
+ for (element = object->start; json_null != element; element = element->next) {
+ if (json_write_get_string_size(element->name, size)) {
+ /* string was malformed! */
+ return 1;
+ }
+
+ if (json_write_minified_get_value_size(element->value, size)) {
+ /* value was malformed! */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+json_weak int
+json_write_minified_get_value_size(const struct json_value_s *value,
+ size_t *size);
+int json_write_minified_get_value_size(const struct json_value_s *value,
+ size_t *size) {
+ switch (value->type) {
+ default:
+ /* unknown value type found! */
+ return 1;
+ case json_type_number:
+ return json_write_get_number_size((struct json_number_s *)value->payload,
+ size);
+ case json_type_string:
+ return json_write_get_string_size((struct json_string_s *)value->payload,
+ size);
+ case json_type_array:
+ return json_write_minified_get_array_size(
+ (struct json_array_s *)value->payload, size);
+ case json_type_object:
+ return json_write_minified_get_object_size(
+ (struct json_object_s *)value->payload, size);
+ case json_type_true:
+ *size += 4; /* the string "true". */
+ return 0;
+ case json_type_false:
+ *size += 5; /* the string "false". */
+ return 0;
+ case json_type_null:
+ *size += 4; /* the string "null". */
+ return 0;
+ }
+}
+
+json_weak char *json_write_minified_value(const struct json_value_s *value,
+ char *data);
+
+json_weak char *json_write_number(const struct json_number_s *number,
+ char *data);
+char *json_write_number(const struct json_number_s *number, char *data) {
+ json_uintmax_t parsed_number, backup;
+ size_t i;
+
+ if (number->number_size >= 2) {
+ switch (number->number[1]) {
+ default:
+ break;
+ case 'x':
+ case 'X':
+ /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal
+ * so we have to do extra work to convert it to a non-hexadecimal for JSON
+ * output. */
+ parsed_number = json_strtoumax(number->number, json_null, 0);
+
+ /* We need a copy of parsed number twice, so take a backup of it. */
+ backup = parsed_number;
+
+ i = 0;
+
+ while (0 != parsed_number) {
+ parsed_number /= 10;
+ i++;
+ }
+
+ /* Restore parsed_number to its original value stored in the backup. */
+ parsed_number = backup;
+
+ /* Now use backup to take a copy of i, or the length of the string. */
+ backup = i;
+
+ do {
+ *(data + i - 1) = '0' + (char)(parsed_number % 10);
+ parsed_number /= 10;
+ i--;
+ } while (0 != parsed_number);
+
+ data += backup;
+
+ return data;
+ }
+ }
+
+ /* check to see if the number has leading/trailing decimal point. */
+ i = 0;
+
+ /* skip any leading '-'. */
+ if ((i < number->number_size) &&
+ (('+' == number->number[i]) || ('-' == number->number[i]))) {
+ i++;
+ }
+
+ /* check if we have infinity. */
+ if ((i < number->number_size) && ('I' == number->number[i])) {
+ const char *inf = "Infinity";
+ size_t k;
+
+ for (k = i; k < number->number_size; k++) {
+ const char c = *inf++;
+
+ /* Check if we found the Infinity string! */
+ if ('\0' == c) {
+ break;
+ } else if (c != number->number[k]) {
+ break;
+ }
+ }
+
+ if ('\0' == *inf++) {
+ const char *dbl_max;
+
+ /* if we had a leading '-' we need to record it in the JSON output. */
+ if ('-' == number->number[0]) {
+ *data++ = '-';
+ }
+
+ /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */
+ for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) {
+ *data++ = *dbl_max;
+ }
+
+ return data;
+ }
+ }
+
+ /* check if we have nan. */
+ if ((i < number->number_size) && ('N' == number->number[i])) {
+ const char *nan = "NaN";
+ size_t k;
+
+ for (k = i; k < number->number_size; k++) {
+ const char c = *nan++;
+
+ /* Check if we found the NaN string! */
+ if ('\0' == c) {
+ break;
+ } else if (c != number->number[k]) {
+ break;
+ }
+ }
+
+ if ('\0' == *nan++) {
+ /* NaN becomes 0 because JSON can't support it. */
+ *data++ = '0';
+ return data;
+ }
+ }
+
+ /* if we had a leading decimal point. */
+ if ((i < number->number_size) && ('.' == number->number[i])) {
+ i = 0;
+
+ /* skip any leading '+'. */
+ if ('+' == number->number[i]) {
+ i++;
+ }
+
+ /* output the leading '-' if we had one. */
+ if ('-' == number->number[i]) {
+ *data++ = '-';
+ i++;
+ }
+
+ /* insert a '0' to fix the leading decimal point for JSON output. */
+ *data++ = '0';
+
+ /* and output the rest of the number as normal. */
+ for (; i < number->number_size; i++) {
+ *data++ = number->number[i];
+ }
+
+ return data;
+ }
+
+ for (; i < number->number_size; i++) {
+ const char c = number->number[i];
+ if (!('0' <= c && c <= '9')) {
+ break;
+ }
+ }
+
+ /* if we had a trailing decimal point. */
+ if ((i + 1 == number->number_size) && ('.' == number->number[i])) {
+ i = 0;
+
+ /* skip any leading '+'. */
+ if ('+' == number->number[i]) {
+ i++;
+ }
+
+ /* output the leading '-' if we had one. */
+ if ('-' == number->number[i]) {
+ *data++ = '-';
+ i++;
+ }
+
+ /* and output the rest of the number as normal. */
+ for (; i < number->number_size; i++) {
+ *data++ = number->number[i];
+ }
+
+ /* insert a '0' to fix the trailing decimal point for JSON output. */
+ *data++ = '0';
+
+ return data;
+ }
+
+ i = 0;
+
+ /* skip any leading '+'. */
+ if ('+' == number->number[i]) {
+ i++;
+ }
+
+ for (; i < number->number_size; i++) {
+ *data++ = number->number[i];
+ }
+
+ return data;
+}
+
+json_weak char *json_write_string(const struct json_string_s *string,
+ char *data);
+char *json_write_string(const struct json_string_s *string, char *data) {
+ size_t i;
+
+ *data++ = '"'; /* open the string. */
+
+ for (i = 0; i < string->string_size; i++) {
+ switch (string->string[i]) {
+ case '"':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = '"';
+ break;
+ case '\\':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = '\\';
+ break;
+ case '\b':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = 'b';
+ break;
+ case '\f':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = 'f';
+ break;
+ case '\n':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = 'n';
+ break;
+ case '\r':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = 'r';
+ break;
+ case '\t':
+ *data++ = '\\'; /* escape the control character. */
+ *data++ = 't';
+ break;
+ default:
+ *data++ = string->string[i];
+ break;
+ }
+ }
+
+ *data++ = '"'; /* close the string. */
+
+ return data;
+}
+
+json_weak char *json_write_minified_array(const struct json_array_s *array,
+ char *data);
+char *json_write_minified_array(const struct json_array_s *array, char *data) {
+ struct json_array_element_s *element = json_null;
+
+ *data++ = '['; /* open the array. */
+
+ for (element = array->start; json_null != element; element = element->next) {
+ if (element != array->start) {
+ *data++ = ','; /* ','s seperate each element. */
+ }
+
+ data = json_write_minified_value(element->value, data);
+
+ if (json_null == data) {
+ /* value was malformed! */
+ return json_null;
+ }
+ }
+
+ *data++ = ']'; /* close the array. */
+
+ return data;
+}
+
+json_weak char *json_write_minified_object(const struct json_object_s *object,
+ char *data);
+char *json_write_minified_object(const struct json_object_s *object,
+ char *data) {
+ struct json_object_element_s *element = json_null;
+
+ *data++ = '{'; /* open the object. */
+
+ for (element = object->start; json_null != element; element = element->next) {
+ if (element != object->start) {
+ *data++ = ','; /* ','s seperate each element. */
+ }
+
+ data = json_write_string(element->name, data);
+
+ if (json_null == data) {
+ /* string was malformed! */
+ return json_null;
+ }
+
+ *data++ = ':'; /* ':'s seperate each name/value pair. */
+
+ data = json_write_minified_value(element->value, data);
+
+ if (json_null == data) {
+ /* value was malformed! */
+ return json_null;
+ }
+ }
+
+ *data++ = '}'; /* close the object. */
+
+ return data;
+}
+
+json_weak char *json_write_minified_value(const struct json_value_s *value,
+ char *data);
+char *json_write_minified_value(const struct json_value_s *value, char *data) {
+ switch (value->type) {
+ default:
+ /* unknown value type found! */
+ return json_null;
+ case json_type_number:
+ return json_write_number((struct json_number_s *)value->payload, data);
+ case json_type_string:
+ return json_write_string((struct json_string_s *)value->payload, data);
+ case json_type_array:
+ return json_write_minified_array((struct json_array_s *)value->payload,
+ data);
+ case json_type_object:
+ return json_write_minified_object((struct json_object_s *)value->payload,
+ data);
+ case json_type_true:
+ data[0] = 't';
+ data[1] = 'r';
+ data[2] = 'u';
+ data[3] = 'e';
+ return data + 4;
+ case json_type_false:
+ data[0] = 'f';
+ data[1] = 'a';
+ data[2] = 'l';
+ data[3] = 's';
+ data[4] = 'e';
+ return data + 5;
+ case json_type_null:
+ data[0] = 'n';
+ data[1] = 'u';
+ data[2] = 'l';
+ data[3] = 'l';
+ return data + 4;
+ }
+}
+
+void *json_write_minified(const struct json_value_s *value, size_t *out_size) {
+ size_t size = 0;
+ char *data = json_null;
+ char *data_end = json_null;
+
+ if (json_null == value) {
+ return json_null;
+ }
+
+ if (json_write_minified_get_value_size(value, &size)) {
+ /* value was malformed! */
+ return json_null;
+ }
+
+ size += 1; /* for the '\0' null terminating character. */
+
+ data = (char *)SHEREDOM_JSON_H_malloc(size);
+
+ if (json_null == data) {
+ /* malloc failed! */
+ return json_null;
+ }
+
+ data_end = json_write_minified_value(value, data);
+
+ if (json_null == data_end) {
+ /* bad chi occurred! */
+ SHEREDOM_JSON_H_free(data);
+ return json_null;
+ }
+
+ /* null terminated the string. */
+ *data_end = '\0';
+
+ if (json_null != out_size) {
+ *out_size = size;
+ }
+
+ return data;
+}
+
+json_weak int json_write_pretty_get_value_size(const struct json_value_s *value,
+ size_t depth, size_t indent_size,
+ size_t newline_size,
+ size_t *size);
+
+json_weak int json_write_pretty_get_array_size(const struct json_array_s *array,
+ size_t depth, size_t indent_size,
+ size_t newline_size,
+ size_t *size);
+int json_write_pretty_get_array_size(const struct json_array_s *array,
+ size_t depth, size_t indent_size,
+ size_t newline_size, size_t *size) {
+ struct json_array_element_s *element;
+
+ *size += 1; /* '['. */
+
+ if (0 < array->length) {
+ /* if we have any elements we need to add a newline after our '['. */
+ *size += newline_size;
+
+ *size += array->length - 1; /* ','s seperate each element. */
+
+ for (element = array->start; json_null != element;
+ element = element->next) {
+ /* each element gets an indent. */
+ *size += (depth + 1) * indent_size;
+
+ if (json_write_pretty_get_value_size(element->value, depth + 1,
+ indent_size, newline_size, size)) {
+ /* value was malformed! */
+ return 1;
+ }
+
+ /* each element gets a newline too. */
+ *size += newline_size;
+ }
+
+ /* since we wrote out some elements, need to add a newline and indentation.
+ */
+ /* to the trailing ']'. */
+ *size += depth * indent_size;
+ }
+
+ *size += 1; /* ']'. */
+
+ return 0;
+}
+
+json_weak int
+json_write_pretty_get_object_size(const struct json_object_s *object,
+ size_t depth, size_t indent_size,
+ size_t newline_size, size_t *size);
+int json_write_pretty_get_object_size(const struct json_object_s *object,
+ size_t depth, size_t indent_size,
+ size_t newline_size, size_t *size) {
+ struct json_object_element_s *element;
+
+ *size += 1; /* '{'. */
+
+ if (0 < object->length) {
+ *size += newline_size; /* need a newline next. */
+
+ *size += object->length - 1; /* ','s seperate each element. */
+
+ for (element = object->start; json_null != element;
+ element = element->next) {
+ /* each element gets an indent and newline. */
+ *size += (depth + 1) * indent_size;
+ *size += newline_size;
+
+ if (json_write_get_string_size(element->name, size)) {
+ /* string was malformed! */
+ return 1;
+ }
+
+ *size += 3; /* seperate each name/value pair with " : ". */
+
+ if (json_write_pretty_get_value_size(element->value, depth + 1,
+ indent_size, newline_size, size)) {
+ /* value was malformed! */
+ return 1;
+ }
+ }
+
+ *size += depth * indent_size;
+ }
+
+ *size += 1; /* '}'. */
+
+ return 0;
+}
+
+json_weak int json_write_pretty_get_value_size(const struct json_value_s *value,
+ size_t depth, size_t indent_size,
+ size_t newline_size,
+ size_t *size);
+int json_write_pretty_get_value_size(const struct json_value_s *value,
+ size_t depth, size_t indent_size,
+ size_t newline_size, size_t *size) {
+ switch (value->type) {
+ default:
+ /* unknown value type found! */
+ return 1;
+ case json_type_number:
+ return json_write_get_number_size((struct json_number_s *)value->payload,
+ size);
+ case json_type_string:
+ return json_write_get_string_size((struct json_string_s *)value->payload,
+ size);
+ case json_type_array:
+ return json_write_pretty_get_array_size(
+ (struct json_array_s *)value->payload, depth, indent_size, newline_size,
+ size);
+ case json_type_object:
+ return json_write_pretty_get_object_size(
+ (struct json_object_s *)value->payload, depth, indent_size,
+ newline_size, size);
+ case json_type_true:
+ *size += 4; /* the string "true". */
+ return 0;
+ case json_type_false:
+ *size += 5; /* the string "false". */
+ return 0;
+ case json_type_null:
+ *size += 4; /* the string "null". */
+ return 0;
+ }
+}
+
+json_weak char *json_write_pretty_value(const struct json_value_s *value,
+ size_t depth, const char *indent,
+ const char *newline, char *data);
+
+json_weak char *json_write_pretty_array(const struct json_array_s *array,
+ size_t depth, const char *indent,
+ const char *newline, char *data);
+char *json_write_pretty_array(const struct json_array_s *array, size_t depth,
+ const char *indent, const char *newline,
+ char *data) {
+ size_t k, m;
+ struct json_array_element_s *element;
+
+ *data++ = '['; /* open the array. */
+
+ if (0 < array->length) {
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+
+ for (element = array->start; json_null != element;
+ element = element->next) {
+ if (element != array->start) {
+ *data++ = ','; /* ','s seperate each element. */
+
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+ }
+
+ for (k = 0; k < depth + 1; k++) {
+ for (m = 0; '\0' != indent[m]; m++) {
+ *data++ = indent[m];
+ }
+ }
+
+ data = json_write_pretty_value(element->value, depth + 1, indent, newline,
+ data);
+
+ if (json_null == data) {
+ /* value was malformed! */
+ return json_null;
+ }
+ }
+
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+
+ for (k = 0; k < depth; k++) {
+ for (m = 0; '\0' != indent[m]; m++) {
+ *data++ = indent[m];
+ }
+ }
+ }
+
+ *data++ = ']'; /* close the array. */
+
+ return data;
+}
+
+json_weak char *json_write_pretty_object(const struct json_object_s *object,
+ size_t depth, const char *indent,
+ const char *newline, char *data);
+char *json_write_pretty_object(const struct json_object_s *object, size_t depth,
+ const char *indent, const char *newline,
+ char *data) {
+ size_t k, m;
+ struct json_object_element_s *element;
+
+ *data++ = '{'; /* open the object. */
+
+ if (0 < object->length) {
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+
+ for (element = object->start; json_null != element;
+ element = element->next) {
+ if (element != object->start) {
+ *data++ = ','; /* ','s seperate each element. */
+
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+ }
+
+ for (k = 0; k < depth + 1; k++) {
+ for (m = 0; '\0' != indent[m]; m++) {
+ *data++ = indent[m];
+ }
+ }
+
+ data = json_write_string(element->name, data);
+
+ if (json_null == data) {
+ /* string was malformed! */
+ return json_null;
+ }
+
+ /* " : "s seperate each name/value pair. */
+ *data++ = ' ';
+ *data++ = ':';
+ *data++ = ' ';
+
+ data = json_write_pretty_value(element->value, depth + 1, indent, newline,
+ data);
+
+ if (json_null == data) {
+ /* value was malformed! */
+ return json_null;
+ }
+ }
+
+ for (k = 0; '\0' != newline[k]; k++) {
+ *data++ = newline[k];
+ }
+
+ for (k = 0; k < depth; k++) {
+ for (m = 0; '\0' != indent[m]; m++) {
+ *data++ = indent[m];
+ }
+ }
+ }
+
+ *data++ = '}'; /* close the object. */
+
+ return data;
+}
+
+json_weak char *json_write_pretty_value(const struct json_value_s *value,
+ size_t depth, const char *indent,
+ const char *newline, char *data);
+char *json_write_pretty_value(const struct json_value_s *value, size_t depth,
+ const char *indent, const char *newline,
+ char *data) {
+ switch (value->type) {
+ default:
+ /* unknown value type found! */
+ return json_null;
+ case json_type_number:
+ return json_write_number((struct json_number_s *)value->payload, data);
+ case json_type_string:
+ return json_write_string((struct json_string_s *)value->payload, data);
+ case json_type_array:
+ return json_write_pretty_array((struct json_array_s *)value->payload, depth,
+ indent, newline, data);
+ case json_type_object:
+ return json_write_pretty_object((struct json_object_s *)value->payload,
+ depth, indent, newline, data);
+ case json_type_true:
+ data[0] = 't';
+ data[1] = 'r';
+ data[2] = 'u';
+ data[3] = 'e';
+ return data + 4;
+ case json_type_false:
+ data[0] = 'f';
+ data[1] = 'a';
+ data[2] = 'l';
+ data[3] = 's';
+ data[4] = 'e';
+ return data + 5;
+ case json_type_null:
+ data[0] = 'n';
+ data[1] = 'u';
+ data[2] = 'l';
+ data[3] = 'l';
+ return data + 4;
+ }
+}
+
+void *json_write_pretty(const struct json_value_s *value, const char *indent,
+ const char *newline, size_t *out_size) {
+ size_t size = 0;
+ size_t indent_size = 0;
+ size_t newline_size = 0;
+ char *data = json_null;
+ char *data_end = json_null;
+
+ if (json_null == value) {
+ return json_null;
+ }
+
+ if (json_null == indent) {
+ indent = " "; /* default to two spaces. */
+ }
+
+ if (json_null == newline) {
+ newline = "\n"; /* default to linux newlines. */
+ }
+
+ while ('\0' != indent[indent_size]) {
+ ++indent_size; /* skip non-null terminating characters. */
+ }
+
+ while ('\0' != newline[newline_size]) {
+ ++newline_size; /* skip non-null terminating characters. */
+ }
+
+ if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size,
+ &size)) {
+ /* value was malformed! */
+ return json_null;
+ }
+
+ size += 1; /* for the '\0' null terminating character. */
+
+ data = (char *)SHEREDOM_JSON_H_malloc(size);
+
+ if (json_null == data) {
+ /* malloc failed! */
+ return json_null;
+ }
+
+ data_end = json_write_pretty_value(value, 0, indent, newline, data);
+
+ if (json_null == data_end) {
+ /* bad chi occurred! */
+ SHEREDOM_JSON_H_free(data);
+ return json_null;
+ }
+
+ /* null terminated the string. */
+ *data_end = '\0';
+
+ if (json_null != out_size) {
+ *out_size = size;
+ }
+
+ return data;
+}
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#endif /* SHEREDOM_JSON_H_INCLUDED. */
diff --git a/src/Wellspring.c b/src/Wellspring.c
index b3eff8c..445a94a 100644
--- a/src/Wellspring.c
+++ b/src/Wellspring.c
@@ -95,6 +95,9 @@
#define STBRP_SORT Wellspring_sort
#define STBRP_ASSERT Wellspring_assert
+#define SHEREDOM_JSON_H_malloc Wellspring_malloc
+#define SHEREDOM_JSON_H_free Wellspring_free
+
typedef uint8_t stbtt_uint8;
typedef int8_t stbtt_int8;
typedef uint16_t stbtt_uint16;
@@ -112,48 +115,54 @@ typedef int32_t stbtt_int32;
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
+#include "json.h"
+
#pragma GCC diagnostic warning "-Wunused-function"
#define INITIAL_QUAD_CAPACITY 128
/* Structs */
+typedef struct PackedChar
+{
+ float atlasLeft, atlasTop, atlasRight, atlasBottom;
+ float planeLeft, planeTop, planeRight, planeBottom;
+ float xAdvance;
+} PackedChar;
+
+typedef struct CharRange
+{
+ PackedChar *data;
+ uint32_t firstCodepoint;
+ uint32_t charCount;
+} CharRange;
+
+typedef struct Packer
+{
+ uint32_t width;
+ uint32_t height;
+
+ CharRange *ranges;
+ uint32_t rangeCount;
+} Packer;
+
typedef struct Font
{
uint8_t *fontBytes;
stbtt_fontinfo fontInfo;
- int32_t ascent;
- int32_t descent;
- int32_t lineGap;
+ float ascender;
+ float descender;
+ float lineHeight;
+ float pixelsPerEm;
+ float distanceRange;
+
+ float scale;
+ float kerningScale; // kerning values from stb_tt are in a different scale
+
+ Packer packer;
} Font;
-typedef struct CharRange
-{
- stbtt_packedchar *data;
- uint32_t firstCodepoint;
- uint32_t charCount;
- float fontSize;
-} CharRange;
-
-typedef struct Packer
-{
- Font *font;
- float fontSize;
-
- stbtt_pack_context *context;
- uint8_t *pixels;
- uint32_t width;
- uint32_t height;
- uint32_t strideInBytes;
- uint32_t padding;
-
- float scale; /* precomputed at init */
-
- CharRange *ranges;
- uint32_t rangeCount;
-} Packer;
-
typedef struct Batch
{
Wellspring_Vertex *vertices;
@@ -164,9 +173,15 @@ typedef struct Batch
uint32_t indexCount;
uint32_t indexCapacity;
- Packer *currentPacker;
+ Font *currentFont;
} Batch;
+typedef struct Quad
+{
+ float x0,y0,s0,t0; // top-left
+ float x1,y1,s1,t1; // bottom-right
+} Quad;
+
/* UTF-8 Decoder */
/* Copyright (c) 2008-2009 Bjoern Hoehrmann
@@ -205,6 +220,126 @@ decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
return *state;
}
+/* JSON helpers */
+
+static uint8_t json_object_has_key(const json_object_t *object, const char* name)
+{
+ json_object_element_t *currentElement = object->start;
+ const char* currentName = currentElement->name->string;
+
+ while (SDL_strcmp(currentName, name) != 0)
+ {
+ if (currentElement->next == NULL)
+ {
+ return 0;
+ }
+
+ currentElement = currentElement->next;
+ currentName = currentElement->name->string;
+ }
+
+ return 1;
+}
+
+static json_object_element_t* json_object_get_element_by_name(const json_object_t *object, const char* name)
+{
+ json_object_element_t *currentElement = object->start;
+ const char* currentName = currentElement->name->string;
+
+ while (SDL_strcmp(currentName, name) != 0)
+ {
+ if (currentElement->next == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Key %s not found in JSON!", name);
+ return NULL;
+ }
+
+ currentElement = currentElement->next;
+ currentName = currentElement->name->string;
+ }
+
+ return currentElement;
+}
+
+static json_object_t* json_object_get_object(const json_object_t *object, const char* name)
+{
+ json_object_element_t *element = json_object_get_element_by_name(object, name);
+
+ if (element == NULL)
+ {
+ return NULL;
+ }
+
+ json_object_t *obj = json_value_as_object(element->value);
+
+ if (obj == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not an object!", name);
+ }
+
+ return obj;
+}
+
+static const char* json_object_get_string(const json_object_t *object, const char* name)
+{
+ json_object_element_t *element = json_object_get_element_by_name(object, name);
+
+ if (element == NULL)
+ {
+ return NULL;
+ }
+
+ json_string_t *str = json_value_as_string(element->value);
+
+ if (str == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a string!", name);
+ return NULL;
+ }
+
+ return str->string;
+}
+
+static uint32_t json_object_get_uint(const json_object_t *object, const char* name)
+{
+ json_object_element_t *element = json_object_get_element_by_name(object, name);
+
+ if (element == NULL)
+ {
+ return 0;
+ }
+
+ json_number_t *num = json_value_as_number(element->value);
+
+ if (num == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a number!", name);
+ return 0;
+ }
+
+ return (uint32_t) SDL_strtoul(num->number, NULL, 10);
+}
+
+static double json_object_get_double(const json_object_t *object, const char* name)
+{
+ json_object_element_t *element = json_object_get_element_by_name(object, name);
+
+ if (element == NULL)
+ {
+ return 0;
+ }
+
+ json_number_t *num = json_value_as_number(element->value);
+
+ if (num == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a string!", name);
+ return 0;
+ }
+
+ return SDL_atof(num->number);
+}
+
/* API */
uint32_t Wellspring_LinkedVersion(void)
@@ -214,107 +349,146 @@ uint32_t Wellspring_LinkedVersion(void)
Wellspring_Font* Wellspring_CreateFont(
const uint8_t* fontBytes,
- uint32_t fontBytesLength
+ uint32_t fontBytesLength,
+ const uint8_t *atlasJsonBytes,
+ uint32_t atlasJsonBytesLength,
+ float *pPixelsPerEm,
+ float *pDistanceRange
) {
Font *font = Wellspring_malloc(sizeof(Font));
font->fontBytes = Wellspring_malloc(fontBytesLength);
Wellspring_memcpy(font->fontBytes, fontBytes, fontBytesLength);
stbtt_InitFont(&font->fontInfo, font->fontBytes, 0);
- stbtt_GetFontVMetrics(&font->fontInfo, &font->ascent, &font->descent, &font->lineGap);
+ int stbAscender, stbDescender, stbLineHeight;
+ stbtt_GetFontVMetrics(&font->fontInfo, &stbAscender, &stbDescender, &stbLineHeight);
+
+ json_value_t *jsonRoot = json_parse(atlasJsonBytes, atlasJsonBytesLength);
+ json_object_t *jsonObject = jsonRoot->payload;
+
+ if (jsonObject == NULL)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas JSON is invalid! Bailing!");
+ Wellspring_free(font->fontBytes);
+ Wellspring_free(font);
+ return NULL;
+ }
+
+ if (SDL_strcmp(jsonObject->start->name->string, "atlas") != 0)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas JSON is invalid! Bailing!");
+ Wellspring_free(jsonRoot);
+ Wellspring_free(font->fontBytes);
+ Wellspring_free(font);
+ return NULL;
+ }
+
+ json_object_t *atlasObject = json_value_as_object(jsonObject->start->value);
+ json_object_t *metricsObject = json_value_as_object(jsonObject->start->next->value);
+ json_array_t *glyphsArray = json_value_as_array(jsonObject->start->next->next->value);
+
+ const char* atlasType = json_object_get_string(atlasObject, "type");
+
+ if (SDL_strcmp(atlasType, "msdf") != 0)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas is not MSDF! Bailing!");
+ Wellspring_free(jsonRoot);
+ Wellspring_free(font->fontBytes);
+ Wellspring_free(font);
+ return NULL;
+ }
+
+ font->packer.width = json_object_get_uint(atlasObject, "width");
+ font->packer.height = json_object_get_uint(atlasObject, "height");
+ font->pixelsPerEm = json_object_get_double(atlasObject, "size");
+ font->distanceRange = json_object_get_double(atlasObject, "distanceRange");
+
+ font->ascender = json_object_get_double(metricsObject, "ascender");
+ font->descender = json_object_get_double(metricsObject, "descender");
+ font->lineHeight = json_object_get_double(metricsObject, "lineHeight");
+
+ font->scale = font->pixelsPerEm * 4 / 3; // converting from "points" (dpi) to pixels
+
+ /* Pack unicode ranges */
+
+ font->packer.ranges = Wellspring_malloc(sizeof(CharRange));
+ font->packer.rangeCount = 1;
+ font->packer.ranges[0].data = NULL;
+ font->packer.ranges[0].charCount = 0;
+
+ int32_t charRangeIndex = 0;
+
+ json_array_element_t *currentGlyphElement = glyphsArray->start;
+ while (currentGlyphElement != NULL)
+ {
+ json_object_t *currentGlyphObject = json_value_as_object(currentGlyphElement->value);
+
+ uint32_t codepoint = json_object_get_uint(currentGlyphObject, "unicode");
+
+ if (font->packer.ranges[charRangeIndex].charCount == 0)
+ {
+ // first codepoint on first range
+ font->packer.ranges[charRangeIndex].firstCodepoint = codepoint;
+ }
+ else if (codepoint != font->packer.ranges[charRangeIndex].firstCodepoint + font->packer.ranges[charRangeIndex].charCount)
+ {
+ // codepoint is not continuous, start a new range
+ charRangeIndex += 1;
+ font->packer.rangeCount += 1;
+
+ font->packer.ranges = Wellspring_realloc(font->packer.ranges, sizeof(PackedChar) * (charRangeIndex + 1));
+ font->packer.ranges[charRangeIndex].firstCodepoint = codepoint;
+ }
+
+ font->packer.ranges[charRangeIndex].charCount += 1;
+ font->packer.ranges[charRangeIndex].data = Wellspring_realloc(font->packer.ranges[charRangeIndex].data, sizeof(PackedChar) * font->packer.ranges[charRangeIndex].charCount);
+
+ PackedChar *packedChar = &font->packer.ranges[charRangeIndex].data[font->packer.ranges[charRangeIndex].charCount - 1];
+ packedChar->atlasLeft = 0;
+ packedChar->atlasRight = 0;
+ packedChar->atlasTop = 0;
+ packedChar->atlasBottom = 0;
+ packedChar->planeLeft = 0;
+ packedChar->planeRight = 0;
+ packedChar->planeTop = 0;
+ packedChar->planeBottom = 0;
+
+ packedChar->xAdvance = json_object_get_double(currentGlyphObject, "advance");
+
+ if (json_object_has_key(currentGlyphObject, "atlasBounds"))
+ {
+ json_object_t *boundsObject = json_object_get_object(currentGlyphObject, "atlasBounds");
+
+ packedChar->atlasLeft = json_object_get_double(boundsObject, "left");
+ packedChar->atlasRight = json_object_get_double(boundsObject, "right");
+ packedChar->atlasTop = json_object_get_double(boundsObject, "top");
+ packedChar->atlasBottom = json_object_get_double(boundsObject, "bottom");
+
+ json_object_t *planeObject = json_object_get_object(currentGlyphObject, "planeBounds");
+
+ packedChar->planeLeft = json_object_get_double(planeObject, "left");
+ packedChar->planeRight = json_object_get_double(planeObject, "right");
+ packedChar->planeTop = json_object_get_double(planeObject, "top");
+ packedChar->planeBottom = json_object_get_double(planeObject, "bottom");
+ }
+
+ currentGlyphElement = currentGlyphElement->next;
+ }
+
+ int advanceWidth, bearing;
+ stbtt_GetCodepointHMetrics(&font->fontInfo, font->packer.ranges[0].firstCodepoint, &advanceWidth, &bearing);
+
+ font->kerningScale = font->packer.ranges[0].data[0].xAdvance / advanceWidth;
+
+ Wellspring_free(jsonRoot);
+
+ *pPixelsPerEm = font->pixelsPerEm;
+ *pDistanceRange = font->distanceRange;
return (Wellspring_Font*) font;
}
-Wellspring_Packer* Wellspring_CreatePacker(
- Wellspring_Font *font,
- float fontSize,
- uint32_t width,
- uint32_t height,
- uint32_t strideInBytes,
- uint32_t padding
-) {
- Packer *packer = Wellspring_malloc(sizeof(Packer));
-
- packer->font = (Font*) font;
- packer->fontSize = fontSize;
-
- packer->context = Wellspring_malloc(sizeof(stbtt_pack_context));
- packer->pixels = Wellspring_malloc(sizeof(uint8_t) * width * height);
-
- packer->width = width;
- packer->height = height;
- packer->strideInBytes = strideInBytes;
- packer->padding = padding;
-
- packer->ranges = NULL;
- packer->rangeCount = 0;
-
- packer->scale = stbtt_ScaleForPixelHeight(&packer->font->fontInfo, fontSize);
-
- stbtt_PackBegin(packer->context, packer->pixels, width, height, strideInBytes, padding, NULL);
-
- return (Wellspring_Packer*) packer;
-}
-
-uint32_t Wellspring_PackFontRanges(
- Wellspring_Packer *packer,
- Wellspring_FontRange *ranges,
- uint32_t numRanges
-) {
- Packer *myPacker = (Packer*) packer;
- Wellspring_FontRange *currentFontRange;
- stbtt_pack_range* stbPackRanges = Wellspring_malloc(sizeof(stbtt_pack_range) * numRanges);
- CharRange *currentCharRange;
- uint32_t i;
-
- for (i = 0; i < numRanges; i += 1)
- {
- currentFontRange = &ranges[i];
- stbPackRanges[i].font_size = myPacker->fontSize;
- stbPackRanges[i].first_unicode_codepoint_in_range = currentFontRange->firstCodepoint;
- stbPackRanges[i].array_of_unicode_codepoints = NULL;
- stbPackRanges[i].num_chars = currentFontRange->numChars;
- stbPackRanges[i].h_oversample = currentFontRange->oversampleH;
- stbPackRanges[i].v_oversample = currentFontRange->oversampleV;
- stbPackRanges[i].chardata_for_range = Wellspring_malloc(sizeof(stbtt_packedchar) * currentFontRange->numChars);
- }
-
- if (!stbtt_PackFontRanges(myPacker->context, myPacker->font->fontBytes, 0, stbPackRanges, numRanges))
- {
- /* Font packing failed, time to bail */
- for (i = 0; i < numRanges; i += 1)
- {
- Wellspring_free(stbPackRanges[i].chardata_for_range);
- }
- return 0;
- }
-
- myPacker->ranges = Wellspring_realloc(myPacker->ranges, sizeof(CharRange) * (myPacker->rangeCount + numRanges));
-
- for (i = 0; i < numRanges; i += 1)
- {
- currentCharRange = &myPacker->ranges[myPacker->rangeCount + i];
- currentCharRange->data = stbPackRanges[i].chardata_for_range;
- currentCharRange->firstCodepoint = stbPackRanges[i].first_unicode_codepoint_in_range;
- currentCharRange->charCount = stbPackRanges[i].num_chars;
- currentCharRange->fontSize = stbPackRanges[i].font_size;
- }
-
- myPacker->rangeCount += numRanges;
-
- Wellspring_free(stbPackRanges);
- return 1;
-}
-
-uint8_t* Wellspring_GetPixelDataPointer(
- Wellspring_Packer *packer
-) {
- Packer* myPacker = (Packer*) packer;
- return myPacker->pixels;
-}
-
-Wellspring_TextBatch* Wellspring_CreateTextBatch()
+Wellspring_TextBatch* Wellspring_CreateTextBatch(void)
{
Batch *batch = Wellspring_malloc(sizeof(Batch));
@@ -331,10 +505,10 @@ Wellspring_TextBatch* Wellspring_CreateTextBatch()
void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch,
- Wellspring_Packer *packer
+ Wellspring_Font *font
) {
Batch *batch = (Batch*) textBatch;
- batch->currentPacker = (Packer*) packer;
+ batch->currentFont = (Font*) font;
batch->vertexCount = 0;
batch->indexCount = 0;
}
@@ -350,15 +524,15 @@ static float Wellspring_INTERNAL_GetVerticalAlignOffset(
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP)
{
- return scale * font->ascent;
+ return scale * font->ascender;
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE)
{
- return scale * (font->ascent + font->descent) / 2.0f;
+ return scale * (font->ascender + font->descender) / 2.0f;
}
else /* BOTTOM */
{
- return scale * font->descent;
+ return scale * font->descender;
}
}
@@ -384,10 +558,40 @@ static inline uint32_t IsWhitespace(uint32_t codepoint)
}
}
+static void GetPackedQuad(PackedChar *charData, float scale, int packerWidth, int packerHeight, int charIndex, float *xPos, float *yPos, Quad *q)
+{
+ float texelWidth = 1.0f / packerWidth, texelHeight = 1.0f / packerHeight;
+ PackedChar *b = charData + charIndex;
+
+ float pl, pb, pr, pt;
+ float il, ib, ir, it;
+
+ pl = *xPos + b->planeLeft * scale;
+ pb = *yPos + b->planeBottom * scale;
+ pr = *xPos + b->planeRight * scale;
+ pt = *yPos + b->planeTop * scale;
+
+ il = b->atlasLeft * texelWidth;
+ ib = b->atlasBottom * texelHeight;
+ ir = b->atlasRight * texelWidth;
+ it = b->atlasTop * texelHeight;
+
+ q->x0 = pl;
+ q->y0 = pt;
+ q->x1 = pr;
+ q->y1 = pb;
+
+ q->s0 = il;
+ q->t0 = it;
+ q->s1 = ir;
+ q->t1 = ib;
+
+ *xPos += b->xAdvance * scale;
+}
+
static uint8_t Wellspring_Internal_TextBounds(
- Packer* packer,
- float x,
- float y,
+ Font* font,
+ int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes,
@@ -397,20 +601,21 @@ static uint8_t Wellspring_Internal_TextBounds(
uint32_t decodeState = 0;
uint32_t codepoint;
int32_t glyphIndex;
- int32_t previousGlyphIndex;
+ int32_t previousGlyphIndex = -1;
int32_t rangeIndex;
- stbtt_packedchar* rangeData;
- float rangeFontSize;
- stbtt_aligned_quad charQuad;
+ PackedChar* rangeData;
+ Quad charQuad;
uint32_t i, j;
+ float x = 0, y = 0;
float minX = x;
float minY = y;
float maxX = x;
float maxY = y;
float startX = x;
float advance = 0;
+ float sizeFactor = pixelSize / font->pixelsPerEm;
- y += Wellspring_INTERNAL_GetVerticalAlignOffset(packer->font, verticalAlignment, packer->scale);
+ y -= Wellspring_INTERNAL_GetVerticalAlignOffset(font, verticalAlignment, sizeFactor * font->scale);
for (i = 0; i < strLengthInBytes; i += 1)
{
@@ -425,17 +630,10 @@ static uint8_t Wellspring_Internal_TextBounds(
continue;
}
- if (IsWhitespace(codepoint))
- {
- int32_t ws_adv, ws_bearing;
- stbtt_GetCodepointHMetrics(&packer->font->fontInfo, codepoint, &ws_adv, &ws_bearing);
- x += packer->scale * ws_adv;
- maxX += packer->scale * ws_adv;
- continue;
- }
-
rangeData = NULL;
+ Packer *packer = &font->packer;
+
/* Find the packed char data */
for (j = 0; j < packer->rangeCount; j += 1)
{
@@ -445,7 +643,6 @@ static uint8_t Wellspring_Internal_TextBounds(
) {
rangeData = packer->ranges[j].data;
rangeIndex = codepoint - packer->ranges[j].firstCodepoint;
- rangeFontSize = packer->ranges[j].fontSize;
break;
}
}
@@ -456,22 +653,31 @@ static uint8_t Wellspring_Internal_TextBounds(
return 0;
}
- glyphIndex = stbtt_FindGlyphIndex(&packer->font->fontInfo, codepoint);
-
- if (i > 0)
+ if (IsWhitespace(codepoint))
{
- x += packer->scale * stbtt_GetGlyphKernAdvance(&packer->font->fontInfo, previousGlyphIndex, glyphIndex);
+ PackedChar *packedChar = rangeData + rangeIndex;
+ x += sizeFactor * font->scale * packedChar->xAdvance;
+ maxX += sizeFactor * font->scale * packedChar->xAdvance;
+ previousGlyphIndex = -1;
+ continue;
}
- stbtt_GetPackedQuad(
+ glyphIndex = stbtt_FindGlyphIndex(&font->fontInfo, codepoint);
+
+ if (previousGlyphIndex != -1)
+ {
+ x += sizeFactor * font->kerningScale * font->scale * stbtt_GetGlyphKernAdvance(&font->fontInfo, previousGlyphIndex, glyphIndex);
+ }
+
+ GetPackedQuad(
rangeData,
+ sizeFactor * font->scale,
packer->width,
packer->height,
rangeIndex,
&x,
&y,
- &charQuad,
- 0
+ &charQuad
);
if (charQuad.x0 < minX) { minX = charQuad.x0; }
@@ -504,9 +710,8 @@ static uint8_t Wellspring_Internal_TextBounds(
}
uint8_t Wellspring_TextBounds(
- Wellspring_Packer* packer,
- float x,
- float y,
+ Wellspring_Font *font,
+ int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes,
@@ -514,9 +719,8 @@ uint8_t Wellspring_TextBounds(
Wellspring_Rectangle* pRectangle
) {
return Wellspring_Internal_TextBounds(
- (Packer*) packer,
- x,
- y,
+ (Font*) font,
+ pixelSize,
horizontalAlignment,
verticalAlignment,
strBytes,
@@ -525,11 +729,9 @@ uint8_t Wellspring_TextBounds(
);
}
-uint8_t Wellspring_Draw(
+uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch,
- float x,
- float y,
- float depth,
+ int pixelSize,
Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
@@ -537,26 +739,28 @@ uint8_t Wellspring_Draw(
uint32_t strLengthInBytes
) {
Batch *batch = (Batch*) textBatch;
- Packer *myPacker = batch->currentPacker;
+ Font *font = batch->currentFont;
+ Packer *myPacker = &font->packer;
uint32_t decodeState = 0;
uint32_t codepoint;
int32_t glyphIndex;
- int32_t previousGlyphIndex;
+ int32_t previousGlyphIndex = -1;
int32_t rangeIndex;
- stbtt_packedchar *rangeData;
- float rangeFontSize;
- stbtt_aligned_quad charQuad;
+ PackedChar *rangeData;
+ Quad charQuad;
uint32_t vertexBufferIndex;
uint32_t indexBufferIndex;
Wellspring_Rectangle bounds;
uint32_t i, j;
+ float sizeFactor = pixelSize / font->pixelsPerEm;
+ float x = 0, y = 0;
- y += Wellspring_INTERNAL_GetVerticalAlignOffset(myPacker->font, verticalAlignment, myPacker->scale);
+ y -= Wellspring_INTERNAL_GetVerticalAlignOffset(font, verticalAlignment, sizeFactor * font->scale);
/* FIXME: If we horizontally align, we have to decode and process glyphs twice, very inefficient. */
if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT)
{
- if (!Wellspring_Internal_TextBounds(myPacker, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
+ if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
@@ -566,7 +770,7 @@ uint8_t Wellspring_Draw(
}
else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER)
{
- if (!Wellspring_Internal_TextBounds(myPacker, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
+ if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
@@ -588,14 +792,6 @@ uint8_t Wellspring_Draw(
continue;
}
- if (IsWhitespace(codepoint))
- {
- int32_t ws_adv, ws_bearing;
- stbtt_GetCodepointHMetrics(&myPacker->font->fontInfo, codepoint, &ws_adv, &ws_bearing);
- x += myPacker->scale * ws_adv;
- continue;
- }
-
rangeData = NULL;
/* Find the packed char data */
@@ -607,7 +803,6 @@ uint8_t Wellspring_Draw(
) {
rangeData = myPacker->ranges[j].data;
rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint;
- rangeFontSize = myPacker->ranges[j].fontSize;
break;
}
}
@@ -618,22 +813,30 @@ uint8_t Wellspring_Draw(
return 0;
}
- glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint);
-
- if (i > 0)
+ if (IsWhitespace(codepoint))
{
- x += myPacker->scale * stbtt_GetGlyphKernAdvance(&myPacker->font->fontInfo, previousGlyphIndex, glyphIndex);
+ PackedChar *packedChar = rangeData + rangeIndex;
+ x += sizeFactor * font->scale * packedChar->xAdvance;
+ previousGlyphIndex = -1;
+ continue;
}
- stbtt_GetPackedQuad(
+ glyphIndex = stbtt_FindGlyphIndex(&font->fontInfo, codepoint);
+
+ if (previousGlyphIndex != -1)
+ {
+ x += sizeFactor * font->kerningScale * font->scale * stbtt_GetGlyphKernAdvance(&font->fontInfo, previousGlyphIndex, glyphIndex);
+ }
+
+ GetPackedQuad(
rangeData,
+ sizeFactor * font->scale,
myPacker->width,
myPacker->height,
rangeIndex,
&x,
&y,
- &charQuad,
- 0
+ &charQuad
);
if (batch->vertexCount >= batch->vertexCapacity)
@@ -648,14 +851,12 @@ uint8_t Wellspring_Draw(
batch->indices = Wellspring_realloc(batch->indices, sizeof(uint32_t) * batch->indexCapacity);
}
- /* TODO: kerning and alignment */
-
vertexBufferIndex = batch->vertexCount;
indexBufferIndex = batch->indexCount;
batch->vertices[vertexBufferIndex].x = charQuad.x0;
batch->vertices[vertexBufferIndex].y = charQuad.y0;
- batch->vertices[vertexBufferIndex].z = depth;
+ batch->vertices[vertexBufferIndex].z = 0;
batch->vertices[vertexBufferIndex].u = charQuad.s0;
batch->vertices[vertexBufferIndex].v = charQuad.t0;
batch->vertices[vertexBufferIndex].r = color->r;
@@ -665,7 +866,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 1].x = charQuad.x0;
batch->vertices[vertexBufferIndex + 1].y = charQuad.y1;
- batch->vertices[vertexBufferIndex + 1].z = depth;
+ batch->vertices[vertexBufferIndex + 1].z = 0;
batch->vertices[vertexBufferIndex + 1].u = charQuad.s0;
batch->vertices[vertexBufferIndex + 1].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 1].r = color->r;
@@ -675,7 +876,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 2].x = charQuad.x1;
batch->vertices[vertexBufferIndex + 2].y = charQuad.y0;
- batch->vertices[vertexBufferIndex + 2].z = depth;
+ batch->vertices[vertexBufferIndex + 2].z = 0;
batch->vertices[vertexBufferIndex + 2].u = charQuad.s1;
batch->vertices[vertexBufferIndex + 2].v = charQuad.t0;
batch->vertices[vertexBufferIndex + 2].r = color->r;
@@ -685,7 +886,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 3].x = charQuad.x1;
batch->vertices[vertexBufferIndex + 3].y = charQuad.y1;
- batch->vertices[vertexBufferIndex + 3].z = depth;
+ batch->vertices[vertexBufferIndex + 3].z = 0;
batch->vertices[vertexBufferIndex + 3].u = charQuad.s1;
batch->vertices[vertexBufferIndex + 3].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 3].r = color->r;
@@ -733,27 +934,15 @@ void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch)
Wellspring_free(batch);
}
-void Wellspring_DestroyPacker(Wellspring_Packer *packer)
-{
- Packer* myPacker = (Packer*) packer;
- uint32_t i;
-
- stbtt_PackEnd(myPacker->context);
-
- for (i = 0; i < myPacker->rangeCount; i += 1)
- {
- Wellspring_free(myPacker->ranges[i].data);
- }
-
- Wellspring_free(myPacker->ranges);
- Wellspring_free(myPacker->context);
- Wellspring_free(myPacker->pixels);
-}
-
void Wellspring_DestroyFont(Wellspring_Font* font)
{
Font *myFont = (Font*) font;
+ for (int i = 0; i < myFont->packer.rangeCount; i += 1)
+ {
+ Wellspring_free(myFont->packer.ranges[i].data);
+ }
+ Wellspring_free(myFont->packer.ranges);
Wellspring_free(myFont->fontBytes);
Wellspring_free(myFont);
}
diff --git a/visualc/Wellspring.sln b/visualc/Wellspring.sln
deleted file mode 100644
index cb45ad7..0000000
--- a/visualc/Wellspring.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30717.126
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Wellspring", "Wellspring.vcxproj", "{6DB15344-E000-45CB-A48A-1D72F7D6E945}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|x64 = Debug|x64
- MinSizeRel|x64 = MinSizeRel|x64
- Release|x64 = Release|x64
- RelWithDebInfo|x64 = RelWithDebInfo|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.Debug|x64.ActiveCfg = Debug|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.Debug|x64.Build.0 = Debug|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.Release|x64.ActiveCfg = Release|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.Release|x64.Build.0 = Release|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {7B2DB465-0A55-3811-9EF4-A520B47653D2}
- EndGlobalSection
-EndGlobal
diff --git a/visualc/Wellspring.vcxproj b/visualc/Wellspring.vcxproj
deleted file mode 100644
index 1fbcb58..0000000
--- a/visualc/Wellspring.vcxproj
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- {6DB15344-E000-45CB-A48A-1D72F7D6E945}
- FNA3D
-
-
-
- DynamicLibrary
- true
- MultiByte
-
-
- DynamicLibrary
- false
- true
- MultiByte
-
-
- v142
-
-
- v142
-
-
-
-
-
-
-
-
-
- ..\lib\;..\..\SDL2\include;..\include;$(IncludePath)
- ..\..\SDL2\lib\$(PlatformShortName);$(LibraryPath)
-
-
-
- Level3
- Disabled
- USE_SDL2;%(PreprocessorDefinitions)
-
-
- DebugFull
- SDL2.lib;%(AdditionalDependencies)
-
-
-
-
- Level3
- MaxSpeed
- USE_SDL2;%(PreprocessorDefinitions)
- true
- true
-
-
- true
- true
- SDL2.lib;%(AdditionalDependencies)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/visualc/Wellspring.vcxproj.user b/visualc/Wellspring.vcxproj.user
deleted file mode 100644
index 88a5509..0000000
--- a/visualc/Wellspring.vcxproj.user
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file