aboutsummaryrefslogtreecommitdiff
path: root/tools/Vulkan-Tools/vulkaninfo/outputprinter.h
diff options
context:
space:
mode:
authorLizzy Fleckenstein <lizzy@vlhl.dev>2026-03-31 01:30:36 +0200
committerLizzy Fleckenstein <lizzy@vlhl.dev>2026-03-31 01:30:36 +0200
commit8e2ff15dbd3fe70fe2b52397b1eaba3fe2d7a5e8 (patch)
tree925fa596210d1a1f01e00e0743a643f4552e7a7a /tools/Vulkan-Tools/vulkaninfo/outputprinter.h
parent1f17b4df127bd280e50d93a46ae93df704adc2b0 (diff)
parent90bf5bc4fd8bea0d300f6564af256a51a34124b8 (diff)
downloadusermoji-8e2ff15dbd3fe70fe2b52397b1eaba3fe2d7a5e8.tar.xz
add tools/Vulkan-Tools
Diffstat (limited to 'tools/Vulkan-Tools/vulkaninfo/outputprinter.h')
-rw-r--r--tools/Vulkan-Tools/vulkaninfo/outputprinter.h829
1 files changed, 829 insertions, 0 deletions
diff --git a/tools/Vulkan-Tools/vulkaninfo/outputprinter.h b/tools/Vulkan-Tools/vulkaninfo/outputprinter.h
new file mode 100644
index 00000000..8255e928
--- /dev/null
+++ b/tools/Vulkan-Tools/vulkaninfo/outputprinter.h
@@ -0,0 +1,829 @@
+/*
+ * Copyright (c) 2019 The Khronos Group Inc.
+ * Copyright (c) 2019 Valve Corporation
+ * Copyright (c) 2019 LunarG, Inc.
+ * Copyright (c) 2023 RasterGrid Kft.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Charles Giessen <charles@lunarg.com>
+ *
+ */
+
+#pragma once
+
+#include <iomanip>
+#include <iostream>
+#include <ostream>
+#include <stack>
+#include <sstream>
+#include <string>
+
+#include <assert.h>
+
+#include "vulkaninfo.h"
+
+std::string insert_quotes(std::string s) { return "\"" + s + "\""; }
+
+std::string to_string(const std::array<uint8_t, 16> &uid) {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0');
+ for (int i = 0; i < 16; ++i) {
+ if (i == 4 || i == 6 || i == 8 || i == 10) ss << '-';
+ ss << std::setw(2) << static_cast<unsigned>(uid[i]);
+ }
+ return ss.str();
+}
+std::string to_string(const std::array<uint8_t, 8> &uid) {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0');
+ for (int i = 0; i < 8; ++i) {
+ if (i == 4) ss << '-';
+ ss << std::setw(2) << static_cast<unsigned>(uid[i]);
+ }
+ return ss.str();
+}
+
+std::ostream &operator<<(std::ostream &out, const VkConformanceVersion &v) {
+ return out << static_cast<unsigned>(v.major) << "." << static_cast<unsigned>(v.minor) << "."
+ << static_cast<unsigned>(v.subminor) << "." << static_cast<unsigned>(v.patch);
+}
+
+enum class OutputType { text, html, json, vkconfig_output };
+
+struct PrinterCreateDetails {
+ OutputType output_type = OutputType::text;
+ bool print_to_file = false;
+ std::string file_name = APP_SHORT_NAME ".txt";
+ std::string start_string = "";
+};
+
+class Printer {
+ public:
+ Printer(const PrinterCreateDetails &details, std::ostream &out, const APIVersion vulkan_version)
+ : output_type(details.output_type), out(out) {
+ StackNode node{};
+ node.is_first_item = false;
+ node.indents = 0;
+ switch (output_type) {
+ case (OutputType::text):
+ out << std::string(strlen(APP_UPPER_CASE_NAME), '=') << "\n";
+ out << APP_UPPER_CASE_NAME "\n";
+ out << std::string(strlen(APP_UPPER_CASE_NAME), '=') << "\n\n";
+ out << API_NAME " Instance Version: " << APIVersion(vulkan_version) << "\n\n\n";
+ break;
+ case (OutputType::html):
+ out << "<!doctype html>\n";
+ out << "<html lang='en'>\n";
+ out << "\t<head>\n";
+ out << "\t\t<title>" APP_SHORT_NAME "</title>\n";
+ out << "\t\t<style>\n";
+ out << "\t\thtml {\n";
+ out << "\t\t\tbackground-color: #0b1e48;\n";
+ out << "\t\t\tbackground-image: url(\"https://vulkan.lunarg.com/img/bg-starfield.jpg\");\n";
+ out << "\t\t\tbackground-position: center;\n";
+ out << "\t\t\t-webkit-background-size: cover;\n";
+ out << "\t\t\t-moz-background-size: cover;\n";
+ out << "\t\t\t-o-background-size: cover;\n";
+ out << "\t\t\tbackground-size: cover;\n";
+ out << "\t\t\tbackground-attachment: fixed;\n";
+ out << "\t\t\tbackground-repeat: no-repeat;\n";
+ out << "\t\t\theight: 100%;\n";
+ out << "\t\t}\n";
+ out << "\t\t#header {\n";
+ out << "\t\t\tz-index: -1;\n";
+ out << "\t\t}\n";
+ out << "\t\t#header>img {\n";
+ out << "\t\t\tposition: absolute;\n";
+ out << "\t\t\twidth: 160px;\n";
+ out << "\t\t\tmargin-left: -280px;\n";
+ out << "\t\t\ttop: -10px;\n";
+ out << "\t\t\tleft: 50%;\n";
+ out << "\t\t}\n";
+ out << "\t\t#header>h1 {\n";
+ out << "\t\t\tfont-family: Arial, \"Helvetica Neue\", Helvetica, sans-serif;\n";
+ out << "\t\t\tfont-size: 44px;\n";
+ out << "\t\t\tfont-weight: 200;\n";
+ out << "\t\t\ttext-shadow: 4px 4px 5px #000;\n";
+ out << "\t\t\tcolor: #eee;\n";
+ out << "\t\t\tposition: absolute;\n";
+ out << "\t\t\twidth: 400px;\n";
+ out << "\t\t\tmargin-left: -80px;\n";
+ out << "\t\t\ttop: 8px;\n";
+ out << "\t\t\tleft: 50%;\n";
+ out << "\t\t}\n";
+ out << "\t\tbody {\n";
+ out << "\t\t\tfont-family: Consolas, monaco, monospace;\n";
+ out << "\t\t\tfont-size: 14px;\n";
+ out << "\t\t\tline-height: 20px;\n";
+ out << "\t\t\tcolor: #eee;\n";
+ out << "\t\t\theight: 100%;\n";
+ out << "\t\t\tmargin: 0;\n";
+ out << "\t\t\toverflow: hidden;\n";
+ out << "\t\t}\n";
+ out << "\t\t#wrapper {\n";
+ out << "\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n";
+ out << "\t\t\tborder: 1px solid #446;\n";
+ out << "\t\t\tbox-shadow: 0px 0px 10px #000;\n";
+ out << "\t\t\tpadding: 8px 12px;\n\n";
+ out << "\t\t\tdisplay: inline-block;\n";
+ out << "\t\t\tposition: absolute;\n";
+ out << "\t\t\ttop: 80px;\n";
+ out << "\t\t\tbottom: 25px;\n";
+ out << "\t\t\tleft: 50px;\n";
+ out << "\t\t\tright: 50px;\n";
+ out << "\t\t\toverflow: auto;\n";
+ out << "\t\t}\n";
+ out << "\t\tdetails>details {\n";
+ out << "\t\t\tmargin-left: 22px;\n";
+ out << "\t\t}\n";
+ out << "\t\tdetails>summary:only-child::-webkit-details-marker {\n";
+ out << "\t\t\tdisplay: none;\n";
+ out << "\t\t}\n";
+ out << "\t\t.var, .type, .val {\n";
+ out << "\t\t\tdisplay: inline;\n";
+ out << "\t\t}\n";
+ out << "\t\t.var {\n";
+ out << "\t\t}\n";
+ out << "\t\t.type {\n";
+ out << "\t\t\tcolor: #acf;\n";
+ out << "\t\t\tmargin: 0 12px;\n";
+ out << "\t\t}\n";
+ out << "\t\t.val {\n";
+ out << "\t\t\tcolor: #afa;\n";
+ out << "\t\t\tbackground: #222;\n";
+ out << "\t\t\ttext-align: right;\n";
+ out << "\t\t}\n";
+ out << "\t\t</style>\n";
+ out << "\t</head>\n";
+ out << "\t<body>\n";
+ out << "\t\t<div id='header'>\n";
+ out << "\t\t\t<h1>" APP_SHORT_NAME "</h1>\n";
+ out << "\t\t</div>\n";
+ out << "\t\t<div id='wrapper'>\n";
+ out << "\t\t\t<details><summary>" API_NAME " Instance Version: <span class='val'>" << APIVersion(vulkan_version)
+ << "</span></summary></details>\n\t\t\t<br />\n";
+ node.indents = 3;
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ out << details.start_string;
+ node.indents = 1;
+ break;
+ default:
+ break;
+ }
+ object_stack.push(node);
+ }
+ ~Printer() {
+ switch (output_type) {
+ case (OutputType::text):
+ break;
+ case (OutputType::html):
+ out << "\t\t</div>\n";
+ out << "\t</body>\n";
+ out << "</html>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ out << "\n}\n";
+ break;
+ }
+ assert(!object_stack.empty() && "mismatched number of ObjectStart/ObjectEnd or ArrayStart/ArrayEnd's");
+ object_stack.pop();
+ assert(object_stack.empty() && "indents must be zero at program end");
+ };
+
+ Printer(const Printer &) = delete;
+ const Printer &operator=(const Printer &) = delete;
+
+ OutputType Type() { return output_type; }
+
+ // When an error occurs, call this to create a valid output file. Needed for json/html
+ void FinishOutput() {
+ while (!object_stack.empty()) {
+ switch (output_type) {
+ case (OutputType::text):
+ break;
+ case (OutputType::html):
+ while (get_top().indents > 3) {
+ out << "</details>\n";
+ }
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ out << "\n" << std::string(static_cast<size_t>(get_top().indents), '\t');
+ if (get_top().is_array) {
+ out << "]";
+ } else {
+ out << "}";
+ }
+ break;
+ default:
+ break;
+ }
+ object_stack.pop();
+ }
+ }
+
+ // Custom Formatting
+ // use by prepending with p.SetXXX().ObjectStart/ArrayStart
+
+ Printer &SetHeader() {
+ get_top().set_next_header = true;
+ return *this;
+ }
+
+ Printer &SetSubHeader() {
+ get_top().set_next_subheader = true;
+ return *this;
+ }
+
+ Printer &SetOpenDetails() {
+ get_top().set_details_open = true;
+ return *this;
+ }
+
+ Printer &SetAlwaysOpenDetails(bool value = true) {
+ get_top().should_always_open = value;
+ return *this;
+ }
+
+ Printer &SetTitleAsType() {
+ get_top().set_object_name_as_type = true;
+ return *this;
+ }
+
+ Printer &SetAsType() {
+ get_top().set_as_type = true;
+ return *this;
+ }
+
+ Printer &SetIgnoreMinWidthInChild() {
+ get_top().ignore_min_width_parameter_in_child = true;
+ return *this;
+ }
+
+ Printer &SetMinKeyWidth(size_t min_key_width) {
+ get_top().min_key_width = min_key_width;
+ return *this;
+ }
+
+ Printer &SetElementIndex(int index) {
+ assert(index >= 0 && "cannot set element index to a negative value");
+ get_top().element_index = index;
+ return *this;
+ }
+ Printer &SetValueDescription(std::string str) {
+ value_description = str;
+ return *this;
+ }
+
+ void ObjectStart(std::string object_name, int32_t count_subobjects = -1) {
+ switch (output_type) {
+ case (OutputType::text): {
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << object_name;
+ if (!value_description.empty()) {
+ out << " (" << value_description << ")";
+ value_description = {};
+ }
+ if (get_top().element_index != -1) {
+ out << "[" << get_top().element_index << "]";
+ }
+ out << ":";
+ if (count_subobjects >= 0) {
+ out << " count = " << count_subobjects;
+ }
+ out << "\n";
+ size_t headersize = object_name.size() + 1;
+ if (count_subobjects >= 0) {
+ headersize += 9 + std::to_string(count_subobjects).size();
+ }
+ if (get_top().element_index != -1) {
+ headersize += 2 + std::to_string(get_top().element_index).size();
+ get_top().element_index = -1;
+ }
+ PrintHeaderUnderlines(headersize);
+ break;
+ }
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+ if (get_top().set_details_open || get_top().should_always_open) {
+ out << "<details open>";
+ get_top().set_details_open = false;
+ } else {
+ out << "<details>";
+ }
+ out << "<summary>";
+ if (get_top().set_object_name_as_type) {
+ out << "<span class='type'>" << object_name << "</span>";
+ get_top().set_object_name_as_type = false;
+ } else {
+ out << object_name;
+ }
+ if (!value_description.empty()) {
+ out << " (" << value_description << ")";
+ value_description = {};
+ }
+ if (get_top().element_index != -1) {
+ out << "[<span class='val'>" << get_top().element_index << "</span>]";
+ get_top().element_index = -1;
+ }
+ if (count_subobjects >= 0) {
+ out << ": count = <span class='val'>" << std::to_string(count_subobjects) << "</span>";
+ }
+ out << "</summary>\n";
+ break;
+ case (OutputType::json):
+ if (!get_top().is_first_item) {
+ out << ",\n";
+ } else {
+ get_top().is_first_item = false;
+ }
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+ // Objects with no name are elements in an array of objects
+ if (get_top().is_array || object_name == "" || get_top().element_index != -1) {
+ out << "{\n";
+ get_top().element_index = -1;
+ } else {
+ out << "\"" << object_name << "\": {\n";
+ }
+ if (!value_description.empty()) {
+ value_description = {};
+ }
+ break;
+ case (OutputType::vkconfig_output):
+ if (!get_top().is_first_item) {
+ out << ",\n";
+ } else {
+ get_top().is_first_item = false;
+ }
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+
+ if (get_top().is_array && get_top().element_index != -1) {
+ out << "\"" << object_name << "[" << get_top().element_index << "]\": ";
+ get_top().element_index = -1;
+ } else if (!get_top().is_array) {
+ out << "\"" << object_name << "\": ";
+ }
+ out << "{\n";
+ if (!value_description.empty()) {
+ value_description = {};
+ }
+ break;
+ default:
+ break;
+ }
+ push_node(false);
+ }
+ void ObjectEnd() {
+ assert(get_top().is_array == false && "cannot call ObjectEnd while inside an Array");
+ object_stack.pop();
+ assert(get_top().indents >= 0 && "indents cannot go below zero");
+ switch (output_type) {
+ case (OutputType::text):
+
+ break;
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << "</details>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ out << "\n" << std::string(static_cast<size_t>(get_top().indents), '\t') << "}";
+ break;
+ default:
+ break;
+ }
+ }
+ void ArrayStart(std::string array_name, size_t element_count = 0) {
+ switch (output_type) {
+ case (OutputType::text): {
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << array_name << ":";
+ size_t underline_count = array_name.size() + 1;
+ if (element_count > 0) {
+ out << " count = " << element_count;
+ underline_count += 9 + std::to_string(element_count).size();
+ }
+ out << "\n";
+ PrintHeaderUnderlines(underline_count);
+ break;
+ }
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+ if (get_top().set_details_open || get_top().should_always_open) {
+ out << "<details open>";
+ get_top().set_details_open = false;
+ } else {
+ out << "<details>";
+ }
+ out << "<summary>" << array_name;
+ if (element_count > 0) {
+ out << ": count = <span class='val'>" << element_count << "</span>";
+ }
+ out << "</summary>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ if (!get_top().is_first_item) {
+ out << ",\n";
+ } else {
+ get_top().is_first_item = false;
+ }
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+ if (!get_top().is_array) {
+ out << "\"" << array_name << "\": ";
+ }
+ out << "[\n";
+ break;
+ default:
+ break;
+ }
+ push_node(true);
+ }
+ void ArrayEnd() {
+ assert(get_top().is_array == true && "cannot call ArrayEnd while inside an Object");
+ object_stack.pop();
+ assert(get_top().indents >= 0 && "indents cannot go below zero");
+ switch (output_type) {
+ case (OutputType::text):
+ break;
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << "</details>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ out << "\n" << std::string(static_cast<size_t>(get_top().indents), '\t') << "]";
+ break;
+ default:
+ break;
+ }
+ }
+
+ // For printing key-value pairs.
+ // value_description is for reference information and is displayed inside parenthesis after the value
+ template <typename T>
+ void PrintKeyValue(std::string key, T value) {
+ // If we are inside of an array, Print the value as an element
+ if (get_top().is_array) {
+ PrintElement(value);
+ return;
+ }
+
+ switch (output_type) {
+ case (OutputType::text):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << key;
+ if (get_top().min_key_width > key.size() && !get_top().ignore_min_width_parameter) {
+ out << std::string(get_top().min_key_width - key.size(), ' ');
+ }
+ out << " = " << value;
+ if (value_description != "") {
+ out << " (" << value_description << ")";
+ value_description = {};
+ }
+ out << "\n";
+ break;
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << "<details><summary>" << key;
+ if (get_top().min_key_width > key.size()) {
+ out << std::string(get_top().min_key_width - key.size(), ' ');
+ }
+ if (get_top().set_as_type) {
+ get_top().set_as_type = false;
+ out << " = <span class='type'>" << value << "</span>";
+ } else {
+ out << " = <span class='val'>" << value << "</span>";
+ }
+ if (!value_description.empty()) {
+ out << " (<span class='val'>" << value_description << "</span>)";
+ value_description = {};
+ }
+ out << "</summary></details>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ if (!get_top().is_first_item) {
+ out << ",\n";
+ } else {
+ get_top().is_first_item = false;
+ }
+ out << std::string(static_cast<size_t>(get_top().indents), '\t');
+ if (!get_top().is_array) {
+ out << "\"" << key << "\": ";
+ }
+ if (!value_description.empty()) {
+ out << "\"" << value << " (" << value_description << ")\"";
+ value_description = {};
+ } else {
+ out << value;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Need a specialization to handle C style arrays since they are implicitly converted to pointers
+ template <size_t N>
+ void PrintKeyValue(std::string key, const uint8_t (&values)[N]) {
+ switch (output_type) {
+ case (OutputType::json): {
+ ArrayStart(key, N);
+ for (uint32_t i = 0; i < N; i++) {
+ PrintElement(static_cast<uint32_t>(values[i]));
+ }
+ ArrayEnd();
+ break;
+ }
+ default: {
+ std::array<uint8_t, N> arr{};
+ std::copy(std::begin(values), std::end(values), std::begin(arr));
+ PrintKeyString(key, to_string(arr));
+ break;
+ }
+ }
+ }
+
+ // For printing key - string pairs (necessary because of json)
+ void PrintKeyString(std::string key, std::string value) {
+ switch (output_type) {
+ case (OutputType::text):
+ case (OutputType::html):
+ PrintKeyValue(key, value);
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ if (!value_description.empty()) {
+ // PrintKeyValue adds the necessary quotes when printing with a value description set
+ PrintKeyValue(key, EscapeJSONCString(value));
+ } else {
+ PrintKeyValue(key, std::string("\"") + EscapeJSONCString(value) + "\"");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // For printing key - string pairs (necessary because of json)
+ void PrintKeyBool(std::string key, bool value) { PrintKeyValue(key, value ? "true" : "false"); }
+
+ // print inside array
+ template <typename T>
+ void PrintElement(T element) {
+ // If we are inside of an object, just use an empty string as the key
+ if (!get_top().is_array) {
+ PrintKeyValue("placeholder", element);
+ return;
+ }
+ switch (output_type) {
+ case (OutputType::text):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << element << "\n";
+ break;
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << "<details><summary>";
+ if (get_top().set_as_type) {
+ get_top().set_as_type = false;
+ out << "<span class='type'>" << element << "</span>";
+ } else {
+ out << "<span class='val'>" << element << "</span>";
+ }
+ out << "</summary></details>\n";
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ if (!get_top().is_first_item) {
+ out << ",\n";
+ } else {
+ get_top().is_first_item = false;
+ }
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << element;
+ break;
+ default:
+ break;
+ }
+ }
+ void PrintString(std::string string) {
+ switch (output_type) {
+ case (OutputType::text):
+ case (OutputType::html):
+ PrintElement(string);
+ break;
+ case (OutputType::json):
+ case (OutputType::vkconfig_output):
+ PrintElement("\"" + EscapeJSONCString(string) + "\"");
+ default:
+ break;
+ }
+ }
+ void PrintExtension(std::string ext_name, uint32_t revision, size_t min_width = 0) {
+ switch (output_type) {
+ case (OutputType::text):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << ext_name
+ << std::string(min_width - ext_name.size(), ' ') << " : extension revision " << revision << "\n";
+ break;
+ case (OutputType::html):
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << "<details><summary>" << DecorateAsType(ext_name)
+ << std::string(min_width - ext_name.size(), ' ') << " : extension revision "
+ << DecorateAsValue(std::to_string(revision)) << "</summary></details>\n";
+ break;
+ case (OutputType::json):
+ PrintKeyValue(ext_name, revision);
+ break;
+ case (OutputType::vkconfig_output):
+ ObjectStart(ext_name);
+ PrintKeyValue("specVersion", revision);
+ ObjectEnd();
+ break;
+ default:
+ break;
+ }
+ }
+ void AddNewline() {
+ if (output_type == OutputType::text) {
+ out << "\n";
+ }
+ }
+ void IndentIncrease() {
+ if (output_type == OutputType::text) {
+ get_top().indents++;
+ }
+ }
+ void IndentDecrease() {
+ if (output_type == OutputType::text) {
+ get_top().indents--;
+ assert(get_top().indents >= 0 && "indents cannot go below zero");
+ }
+ }
+
+ std::string DecorateAsType(const std::string &input) {
+ if (output_type == OutputType::html)
+ return "<span class='type'>" + input + "</span>";
+ else
+ return input;
+ }
+
+ std::string DecorateAsValue(const std::string &input) {
+ if (output_type == OutputType::html)
+ return "<span class='val'>" + input + "</span>";
+ else
+ return input;
+ }
+
+ protected:
+ OutputType output_type;
+ std::ostream &out;
+
+ struct StackNode {
+ int indents = 0;
+
+ // header, subheader
+ bool set_next_header = false;
+ bool set_next_subheader = false;
+
+ // html coloring
+ bool set_as_type = false;
+
+ // open <details>
+ bool set_details_open = false;
+
+ // always open <details>
+ bool should_always_open = false;
+
+ // make object titles the color of types
+ bool set_object_name_as_type = false;
+
+ // ignores the min_width parameter
+ bool ignore_min_width_parameter = false;
+
+ // sets the next created object/child to ignore the min_width parameter
+ bool ignore_min_width_parameter_in_child = false;
+
+ // keep track of the spacing between names and values
+ size_t min_key_width = 0;
+
+ // objects which are in an array
+ int element_index = -1; // negative one is the sentinel value
+
+ // json
+ bool is_first_item; // for json: for adding a comma in between objects
+ bool is_array; // for json: match pairs of {}'s and []'s
+ };
+
+ // Contains the details about the current 'object' in the tree
+ std::stack<StackNode> object_stack;
+
+ // Helper to get the current top of the object_stack
+ StackNode &get_top() { return object_stack.top(); }
+
+ // Optional 'description' for values
+ // Must be set right before the Print() function is called
+ std::string value_description;
+
+ // can only be called after a node was manually pushed onto the stack in the constructor
+ void push_node(bool array) {
+ StackNode node{};
+ node.indents = get_top().indents + 1;
+ node.is_array = array;
+ node.is_first_item = true;
+ node.ignore_min_width_parameter = get_top().ignore_min_width_parameter_in_child;
+ object_stack.push(node);
+ }
+
+ // utility
+ void PrintHeaderUnderlines(size_t length) {
+ assert(get_top().indents >= 0 && "indents must not be negative");
+ assert(length <= 10000 && "length shouldn't be unreasonably large");
+ if (get_top().set_next_header) {
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << std::string(length, '=') << "\n";
+ get_top().set_next_header = false;
+ } else if (get_top().set_next_subheader) {
+ out << std::string(static_cast<size_t>(get_top().indents), '\t') << std::string(length, '-') << "\n";
+ get_top().set_next_subheader = false;
+ }
+ }
+
+ // Replace special characters in strings with their escaped versions.
+ // <https://www.json.org/json-en.html>
+ std::string EscapeJSONCString(std::string string) {
+ if (output_type == OutputType::text || output_type == OutputType::html) return string;
+ std::string out{};
+ for (size_t i = 0; i < string.size(); i++) {
+ char c = string[i];
+ char out_c = c;
+ switch (c) {
+ case '\"':
+ case '\\':
+ out.push_back('\\');
+ break;
+ case '\b':
+ out.push_back('\\');
+ out_c = 'b';
+ break;
+ case '\f':
+ out.push_back('\\');
+ out_c = 'f';
+ break;
+ case '\n':
+ out.push_back('\\');
+ out_c = 'n';
+ break;
+ case '\r':
+ out.push_back('\\');
+ out_c = 'r';
+ break;
+ case '\t':
+ out.push_back('\\');
+ out_c = 't';
+ break;
+ }
+ out.push_back(out_c);
+ }
+
+ return out;
+ }
+};
+// Purpose: When a Printer starts an object or array it will automatically indent the output. This isn't
+// always desired, requiring a manual decrease of indention. This wrapper facilitates that while also
+// automatically re-indenting the output to the previous indent level on scope exit.
+class IndentWrapper {
+ public:
+ IndentWrapper(Printer &p) : p(p) {
+ if (p.Type() == OutputType::text) p.IndentDecrease();
+ }
+ ~IndentWrapper() {
+ if (p.Type() == OutputType::text) p.IndentIncrease();
+ }
+
+ private:
+ Printer &p;
+};
+
+class ObjectWrapper {
+ public:
+ ObjectWrapper(Printer &p, std::string object_name) : p(p) { p.ObjectStart(object_name); }
+ ObjectWrapper(Printer &p, std::string object_name, size_t count_subobjects) : p(p) {
+ p.ObjectStart(object_name, static_cast<int32_t>(count_subobjects));
+ }
+ ~ObjectWrapper() { p.ObjectEnd(); }
+
+ private:
+ Printer &p;
+};
+
+class ArrayWrapper {
+ public:
+ ArrayWrapper(Printer &p, std::string array_name, size_t element_count = 0) : p(p) { p.ArrayStart(array_name, element_count); }
+ ~ArrayWrapper() { p.ArrayEnd(); }
+
+ private:
+ Printer &p;
+};