Tool refactoring
This commit is contained in:
125
archive/tools/tools/CMakeLists.txt
Normal file
125
archive/tools/tools/CMakeLists.txt
Normal file
@ -0,0 +1,125 @@
|
||||
# Copyright (c) 2023 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(texturegen)
|
||||
add_subdirectory(tilesetgen)
|
||||
add_subdirectory(truetypegen)
|
||||
add_subdirectory(languagegen)
|
||||
add_subdirectory(generatedlanguages)
|
||||
|
||||
# Settings
|
||||
set(DAWN_TOOL_GENERATED_LANG_DIR "${DAWN_TEMP_DIR}/languages" CACHE INTERNAL ${DAWN_CACHE_TARGET})
|
||||
set(DAWN_SCENE_ITEM_COMPONENT_LIST "" CACHE INTERNAL ${DAWN_CACHE_TARGET})
|
||||
|
||||
# Texture Tool
|
||||
function(tool_texture target in)
|
||||
add_custom_target(${target}
|
||||
COMMAND texturegen --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_ASSETS_BUILD_DIR}/${target}"
|
||||
COMMENT "Generating texture ${target} from ${in}"
|
||||
DEPENDS texturegen
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
|
||||
# Tileset Tool
|
||||
function(tool_tileset targetTileset targetTexture in cols rows)
|
||||
tool_texture(${targetTexture} ${in})
|
||||
add_custom_target(${targetTileset}
|
||||
COMMAND tilesetgen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${targetTileset}" "${cols}" "${rows}"
|
||||
COMMENT "Generating tileset ${target} from ${in}"
|
||||
DEPENDS tilesetgen ${targetTexture}
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${targetTileset})
|
||||
endfunction()
|
||||
|
||||
# Bitmap Font
|
||||
function(tool_bitmapfont target in columns rows)
|
||||
tool_tileset(${target}_tileset ${target}_texture ${in} ${columns} ${rows})
|
||||
endfunction()
|
||||
|
||||
# TrueType Tool
|
||||
function(tool_truetype target in out width height fontSize)
|
||||
add_custom_target(${target}
|
||||
COMMAND truetypegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}" "${width}" "${height}" "${fontSize}"
|
||||
COMMENT "Generating truetype ${target} from ${in}"
|
||||
DEPENDS truetypegen
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
|
||||
# UI Tool
|
||||
function(tool_ui target in)
|
||||
add_custom_target(${target}
|
||||
COMMAND uigen --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_GENERATED_DIR}/prefabs/ui/${target}"
|
||||
COMMENT "Generating UI ${target} from ${in}"
|
||||
DEPENDS uigen
|
||||
)
|
||||
target_include_directories(${DAWN_TARGET_NAME}
|
||||
PUBLIC
|
||||
${DAWN_GENERATED_DIR}/ui
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
|
||||
# Generated Language Rollup Tool
|
||||
function(tool_generatedlanguages in)
|
||||
add_custom_target(generatedlanguages
|
||||
COMMAND generatedlanguagestool --input="${DAWN_TOOL_GENERATED_LANG_DIR}" --output="${DAWN_ASSETS_BUILD_DIR}"
|
||||
COMMENT "Generating all languages"
|
||||
DEPENDS generatedlanguagestool
|
||||
)
|
||||
if(NOT generatedlanguages IN_LIST DAWN_TARGET_DEPENDENCIES_LAST)
|
||||
set(
|
||||
DAWN_TARGET_DEPENDENCIES_LAST
|
||||
generatedlanguages
|
||||
CACHE INTERNAL ${DAWN_CACHE_TARGET}
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Language Tool
|
||||
function(tool_language target in)
|
||||
add_custom_target(${target}
|
||||
COMMAND languagegen --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_TOOL_GENERATED_LANG_DIR}/${target}.language"
|
||||
COMMENT "Generating language set ${target} from ${in}"
|
||||
DEPENDS languagegen
|
||||
)
|
||||
tool_generatedlanguages("${DAWN_TOOL_GENERATED_LANG_DIR}/${target}.language")
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
|
||||
# Audio Tool
|
||||
if(DAWN_TARGET_OPENAL)
|
||||
add_subdirectory(audiogen)
|
||||
|
||||
function(tool_audio target in)
|
||||
add_custom_target(${target}
|
||||
COMMAND audiogen --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_ASSETS_BUILD_DIR}/${target}"
|
||||
COMMENT "Generating audio ${target} from ${in}"
|
||||
DEPENDS audiogen
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
|
||||
if(DAWN_VISUAL_NOVEL)
|
||||
add_subdirectory(vnscenegen)
|
||||
|
||||
# VN Scene Tool
|
||||
function(tool_vnscene target in)
|
||||
tool_generatedlanguages("${DAWN_TOOL_GENERATED_LANG_DIR}/${target}.language")
|
||||
add_custom_target(${target}
|
||||
COMMAND vnscenegen --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_GENERATED_DIR}/scenes/${target}" --language-out="${DAWN_TOOL_GENERATED_LANG_DIR}/${target}.language"
|
||||
COMMENT "Generating VN Scene ${target} from ${in}"
|
||||
DEPENDS vnscenegen
|
||||
)
|
||||
target_include_directories(${DAWN_TARGET_NAME}
|
||||
PUBLIC
|
||||
${DAWN_GENERATED_DIR}
|
||||
)
|
||||
add_dependencies(${DAWN_TARGET_NAME} ${target})
|
||||
endfunction()
|
||||
endif()
|
60
archive/tools/tools/audiogen/AudioGen.cpp
Normal file
60
archive/tools/tools/audiogen/AudioGen.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "AudioGen.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
std::vector<std::string> AudioGen::getRequiredFlags() {
|
||||
return std::vector<std::string>{ "input", "output" };
|
||||
}
|
||||
|
||||
int32_t AudioGen::start() {
|
||||
// Finished with XML data, now we can write data out.
|
||||
File fileOut(flags["output"] + ".audio");
|
||||
if(fileOut.exists()) return 0;
|
||||
|
||||
// Load input file
|
||||
AudioFile<double> audioFile;
|
||||
if(!audioFile.load(flags["input"])) {
|
||||
printf("Failed to load audio file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Open Output File
|
||||
fileOut.mkdirp();
|
||||
if(!fileOut.open(FILE_MODE_WRITE)) {
|
||||
std::cout << "Failed to open " << fileOut.filename << " output for writing" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write Header
|
||||
char buffer[FILE_BUFFER_SIZE];
|
||||
sprintf(buffer, "%i|%i|%i|%i|",
|
||||
audioFile.getNumChannels(),
|
||||
audioFile.getSampleRate(),
|
||||
audioFile.getNumSamplesPerChannel(),
|
||||
audioFile.getNumSamplesPerChannel() * audioFile.getNumChannels()*(
|
||||
sizeof(int16_t) / sizeof(uint8_t)
|
||||
)
|
||||
);
|
||||
auto bufferLength = strlen(buffer);
|
||||
fileOut.writeRaw(buffer, bufferLength);
|
||||
|
||||
// Convert Data to 16 bit audio
|
||||
for (int32_t i = 0; i < audioFile.getNumSamplesPerChannel(); i++) {
|
||||
for(int32_t y = 0; y < audioFile.getNumChannels(); y++) {
|
||||
double sample = audioFile.samples[y][i];
|
||||
sample = sample < -1 ? -1 : sample > 1 ? 1 : sample;
|
||||
auto q = static_cast<int16_t> (sample * 32767.);
|
||||
|
||||
buffer[0] = (q >> 8) & 0xFF;
|
||||
buffer[1] = q & 0xFF;
|
||||
fileOut.writeRaw(buffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
19
archive/tools/tools/audiogen/AudioGen.hpp
Normal file
19
archive/tools/tools/audiogen/AudioGen.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "util/DawnTool.hpp"
|
||||
#include "util/File.hpp"
|
||||
#include "AudioFile.h"
|
||||
|
||||
namespace Dawn {
|
||||
class AudioGen : public DawnTool {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredFlags() override;
|
||||
|
||||
public:
|
||||
int32_t start();
|
||||
};
|
||||
}
|
38
archive/tools/tools/audiogen/CMakeLists.txt
Normal file
38
archive/tools/tools/audiogen/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2023 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
project(audiogen VERSION 1.0)
|
||||
add_executable(audiogen)
|
||||
|
||||
# Sources
|
||||
target_sources(audiogen
|
||||
PRIVATE
|
||||
${DAWN_SHARED_SOURCES}
|
||||
${DAWN_TOOL_SOURCES}
|
||||
AudioGen.cpp
|
||||
)
|
||||
|
||||
# Includes
|
||||
target_include_directories(audiogen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${DAWN_TOOL_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Definitions
|
||||
target_compile_definitions(audiogen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_DEFINITIONS}
|
||||
DAWN_TOOL_INSTANCE=AudioGen
|
||||
DAWN_TOOL_HEADER="AudioGen.hpp"
|
||||
)
|
||||
|
||||
# Libraries
|
||||
target_link_libraries(audiogen
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
AudioFile
|
||||
)
|
34
archive/tools/tools/generatedlanguages/CMakeLists.txt
Normal file
34
archive/tools/tools/generatedlanguages/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2023 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Generated Languages Tool
|
||||
project(generatedlanguagestool VERSION 1.0)
|
||||
add_executable(generatedlanguagestool)
|
||||
|
||||
target_sources(generatedlanguagestool
|
||||
PRIVATE
|
||||
${DAWN_SHARED_SOURCES}
|
||||
${DAWN_TOOL_SOURCES}
|
||||
GeneratedLanguages.cpp
|
||||
)
|
||||
|
||||
target_include_directories(generatedlanguagestool
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${DAWN_TOOL_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
target_compile_definitions(generatedlanguagestool
|
||||
PUBLIC
|
||||
${DAWN_SHARED_DEFINITIONS}
|
||||
DAWN_TOOL_INSTANCE=GeneratedLanguages
|
||||
DAWN_TOOL_HEADER="GeneratedLanguages.hpp"
|
||||
)
|
||||
|
||||
target_link_libraries(generatedlanguagestool
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
)
|
141
archive/tools/tools/generatedlanguages/GeneratedLanguages.cpp
Normal file
141
archive/tools/tools/generatedlanguages/GeneratedLanguages.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "GeneratedLanguages.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
std::vector<std::string> GeneratedLanguages::getRequiredFlags() {
|
||||
return std::vector<std::string>{ "input", "output" };
|
||||
}
|
||||
|
||||
int32_t GeneratedLanguages::start() {
|
||||
// Generate list of languages
|
||||
std::string inNormal = File::normalizeSlashes(flags["input"]);
|
||||
std::string error;
|
||||
std::vector<std::string> files;
|
||||
int32_t ret = this->scanDir(inNormal, &error, &files);
|
||||
if(ret != 0) {
|
||||
std::cout << error << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Now process each language file
|
||||
std::map<std::string, std::map<std::string, std::string>> strings;
|
||||
|
||||
std::vector<std::string> knownKeys;
|
||||
auto itFiles = files.begin();
|
||||
while(itFiles != files.end()) {
|
||||
File file(*itFiles);
|
||||
file.open(FILE_MODE_READ);
|
||||
std::string buffer;
|
||||
size_t n = 0;
|
||||
|
||||
while(n < file.length) {
|
||||
char lang[32];
|
||||
char key[128];
|
||||
char string[32178];
|
||||
|
||||
// Read lang
|
||||
if(n != 0) file.setPosition(n);
|
||||
auto langSize = file.readAhead(lang, 32, '|');
|
||||
lang[langSize] = '\0';
|
||||
n += langSize + 1;
|
||||
if(langSize <= 0) {
|
||||
std::cout << "Error reading language name: " << langSize << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read Key
|
||||
file.setPosition(n);
|
||||
auto keySize = file.readAhead(key, 128, '|');
|
||||
key[keySize] = '\0';
|
||||
n += keySize + 1;
|
||||
if(keySize <= 0) {
|
||||
std::cout << "Error reading language key: " << keySize << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read String
|
||||
file.setPosition(n);
|
||||
auto stringSize = file.readAhead(string, 32178, '|');
|
||||
string[stringSize] = '\0';
|
||||
n += stringSize + 1;
|
||||
if(stringSize <= 0) {
|
||||
std::cout << "Error reading language string: " << stringSize << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
strings[lang][key] = string;
|
||||
auto exist = std::find(knownKeys.begin(), knownKeys.end(), key);
|
||||
if(exist == knownKeys.end()) knownKeys.push_back(key);
|
||||
}
|
||||
|
||||
++itFiles;
|
||||
}
|
||||
|
||||
// Now prepare output file
|
||||
auto itLang = strings.begin();
|
||||
std::string bufferOut = "";
|
||||
while(itLang != strings.end()) {
|
||||
File langOut(flags["output"] + FILE_PATH_SEP + "language_" + itLang->first + ".language");
|
||||
bufferOut.clear();
|
||||
|
||||
|
||||
auto itKeys = knownKeys.begin();
|
||||
while(itKeys != knownKeys.end()) {
|
||||
auto key = *itKeys;
|
||||
auto exist = itLang->second.find(key);
|
||||
if(exist == itLang->second.end()) {
|
||||
std::cout << "Language " << itLang->first << " is missing key " << key << std::endl;
|
||||
return 1;
|
||||
}
|
||||
bufferOut += exist->first + "|" + exist->second + "|";
|
||||
++itKeys;
|
||||
}
|
||||
|
||||
// Write out.
|
||||
langOut.mkdirp();
|
||||
if(!langOut.writeString(bufferOut)) {
|
||||
std::cout << "Failed to create output file \"" + langOut.filename + "\"" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
++itLang;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t GeneratedLanguages::scanDir(
|
||||
std::string dir,
|
||||
std::string *error,
|
||||
std::vector<std::string> *files
|
||||
) {
|
||||
DIR* handle = opendir(dir.c_str());
|
||||
if(ENOENT == errno) {
|
||||
*error = "Input directory \"" + dir + "\" does not exist";
|
||||
return 0;
|
||||
}
|
||||
if(!handle) return 0;
|
||||
|
||||
struct dirent *entry;
|
||||
while((entry=readdir(handle))) {
|
||||
std::string name(entry->d_name);
|
||||
if(name.size() == 0 || name[0] == '.') continue;
|
||||
auto path = dir + FILE_PATH_SEP + entry->d_name;
|
||||
if(entry->d_type == DT_DIR) {
|
||||
auto ret = this->scanDir(dir, error, files);
|
||||
if(ret != 0) return ret;
|
||||
} else if(entry->d_type == DT_REG) {
|
||||
files->push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(handle);
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "util/DawnTool.hpp"
|
||||
#include "util/Xml.hpp"
|
||||
#include "util/File.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
class GeneratedLanguages : public DawnTool {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredFlags() override;
|
||||
|
||||
int32_t scanDir(
|
||||
std::string dir,
|
||||
std::string *error,
|
||||
std::vector<std::string> *files
|
||||
);
|
||||
|
||||
public:
|
||||
int32_t start() override;
|
||||
};
|
||||
}
|
38
archive/tools/tools/languagegen/CMakeLists.txt
Normal file
38
archive/tools/tools/languagegen/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2021 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Texture Build Tool
|
||||
project(languagegen VERSION 2.0)
|
||||
add_executable(languagegen)
|
||||
|
||||
|
||||
# Sources
|
||||
target_sources(languagegen
|
||||
PRIVATE
|
||||
${DAWN_SHARED_SOURCES}
|
||||
${DAWN_TOOL_SOURCES}
|
||||
LanguageGen.cpp
|
||||
)
|
||||
|
||||
# Includes
|
||||
target_include_directories(languagegen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${DAWN_TOOL_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Definitions
|
||||
target_compile_definitions(languagegen
|
||||
PUBLIC
|
||||
DAWN_TOOL_INSTANCE=LanguageGen
|
||||
DAWN_TOOL_HEADER="LanguageGen.hpp"
|
||||
)
|
||||
|
||||
# Libraries
|
||||
target_link_libraries(languagegen
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
)
|
152
archive/tools/tools/languagegen/LanguageGen.cpp
Normal file
152
archive/tools/tools/languagegen/LanguageGen.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "LanguageGen.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
std::vector<std::string> LanguageParser::getRequiredAttributes() {
|
||||
return std::vector<std::string>{ "key" };
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> LanguageParser::getOptionalAttributes() {
|
||||
return std::map<std::string, std::string>();
|
||||
}
|
||||
|
||||
int32_t LanguageParser::onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageString *out,
|
||||
std::string *error
|
||||
) {
|
||||
out->key = values["key"];
|
||||
out->text = node->value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<std::string> LanguageGroupParser::getRequiredAttributes() {
|
||||
return std::vector<std::string>{ "key" };
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> LanguageGroupParser::getOptionalAttributes() {
|
||||
return std::map<std::string, std::string>();
|
||||
}
|
||||
|
||||
int32_t LanguageGroupParser::onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageGroup *out,
|
||||
std::string *error
|
||||
) {
|
||||
std::string key = values["key"];
|
||||
out->key += key;
|
||||
|
||||
auto it = node->children.begin();
|
||||
int32_t ret;
|
||||
while(it != node->children.end()) {
|
||||
auto c = *it;
|
||||
if(c->node == "string") {
|
||||
struct LanguageString string;
|
||||
ret = (LanguageParser()).parse(c, &string, error);
|
||||
if(ret != 0) return ret;
|
||||
|
||||
string.lang = out->lang;
|
||||
string.key = out->key + "." + string.key;
|
||||
out->strings.push_back(string);
|
||||
|
||||
} else if(c->node == "group") {
|
||||
struct LanguageGroup group;
|
||||
group.key += key + ".";
|
||||
group.lang = out->lang;
|
||||
|
||||
ret = (LanguageGroupParser()).parse(c, &group, error);
|
||||
if(ret != 0) return ret;
|
||||
vectorAppend(&out->strings, group.strings);
|
||||
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<std::string> LanguageRootParser::getRequiredAttributes() {
|
||||
return std::vector<std::string>{ "lang" };
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> LanguageRootParser::getOptionalAttributes() {
|
||||
return std::map<std::string, std::string>();
|
||||
}
|
||||
|
||||
int32_t LanguageRootParser::onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageRoot *out,
|
||||
std::string *error
|
||||
) {
|
||||
int32_t ret;
|
||||
out->lang = values["lang"];
|
||||
|
||||
auto it = node->children.begin();
|
||||
while(it != node->children.end()) {
|
||||
auto c = *it;
|
||||
if(c->node == "string") {
|
||||
struct LanguageString string;
|
||||
ret = (LanguageParser()).parse(c, &string, error);
|
||||
if(ret != 0) return ret;
|
||||
string.lang = out->lang;
|
||||
out->strings.push_back(string);
|
||||
|
||||
} else if(c->node == "group") {
|
||||
struct LanguageGroup group;
|
||||
group.lang = out->lang;
|
||||
ret = (LanguageGroupParser()).parse(c, &group, error);
|
||||
if(ret != 0) return ret;
|
||||
vectorAppend(&out->strings, group.strings);
|
||||
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<std::string> LanguageGen::getRequiredFlags() {
|
||||
return std::vector<std::string>{ "input", "output" };
|
||||
}
|
||||
|
||||
int32_t LanguageGen::start() {
|
||||
auto fileIn = File(flags["input"]);
|
||||
std::string buffer;
|
||||
if(!fileIn.readString(&buffer)) {
|
||||
std::cout << "Failed to open/read input file " << fileIn.filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
auto xml = Xml::load(buffer);
|
||||
std::string error;
|
||||
struct LanguageRoot root;
|
||||
|
||||
auto ret = (LanguageRootParser()).parse(&xml, &root, &error);
|
||||
if(ret != 0) {
|
||||
std::cout << error << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Now dump out the language strings to be picked up later.
|
||||
ret = languageSaveStrings(flags["output"], root.strings);
|
||||
if(ret != 0) return ret;
|
||||
|
||||
return 0;
|
||||
}
|
67
archive/tools/tools/languagegen/LanguageGen.hpp
Normal file
67
archive/tools/tools/languagegen/LanguageGen.hpp
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "util/DawnTool.hpp"
|
||||
#include "util/File.hpp"
|
||||
#include "util/XmlParser.hpp"
|
||||
#include "util/Language.cpp"
|
||||
|
||||
namespace Dawn {
|
||||
struct LanguageGroup {
|
||||
std::vector<struct LanguageString> strings;
|
||||
std::string key;
|
||||
std::string lang;
|
||||
};
|
||||
|
||||
struct LanguageRoot {
|
||||
std::string lang;
|
||||
std::vector<struct LanguageString> strings;
|
||||
};
|
||||
|
||||
class LanguageParser : public XmlParser<struct LanguageString> {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredAttributes() override;
|
||||
std::map<std::string, std::string> getOptionalAttributes() override;
|
||||
int32_t onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageString *out,
|
||||
std::string *error
|
||||
) override;
|
||||
};
|
||||
|
||||
class LanguageGroupParser : public XmlParser<struct LanguageGroup> {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredAttributes() override;
|
||||
std::map<std::string, std::string> getOptionalAttributes() override;
|
||||
int32_t onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageGroup *out,
|
||||
std::string *error
|
||||
) override;
|
||||
};
|
||||
|
||||
class LanguageRootParser : public XmlParser<struct LanguageRoot> {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredAttributes() override;
|
||||
std::map<std::string, std::string> getOptionalAttributes() override;
|
||||
int32_t onParse(
|
||||
Xml *node,
|
||||
std::map<std::string, std::string> values,
|
||||
struct LanguageRoot *out,
|
||||
std::string *error
|
||||
) override;
|
||||
};
|
||||
|
||||
class LanguageGen : public DawnTool {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredFlags() override;
|
||||
|
||||
public:
|
||||
int32_t start() override;
|
||||
};
|
||||
}
|
38
archive/tools/tools/texturegen/CMakeLists.txt
Normal file
38
archive/tools/tools/texturegen/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2021 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Texture Build Tool
|
||||
project(texturegen VERSION 1.0)
|
||||
add_executable(texturegen)
|
||||
|
||||
target_sources(texturegen
|
||||
PRIVATE
|
||||
${DAWN_SHARED_SOURCES}
|
||||
${DAWN_TOOL_SOURCES}
|
||||
TextureGen.cpp
|
||||
../../util/image.cpp
|
||||
)
|
||||
|
||||
target_include_directories(texturegen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${DAWN_TOOL_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Definitions
|
||||
target_compile_definitions(texturegen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_DEFINITIONS}
|
||||
DAWN_TOOL_INSTANCE=TextureGen
|
||||
DAWN_TOOL_HEADER="TextureGen.hpp"
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(texturegen
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
stb
|
||||
)
|
67
archive/tools/tools/texturegen/TextureGen.cpp
Normal file
67
archive/tools/tools/texturegen/TextureGen.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "TextureGen.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
std::vector<std::string> TextureGen::getRequiredFlags() {
|
||||
return std::vector<std::string>{ "input", "output" };
|
||||
}
|
||||
|
||||
int32_t TextureGen::start() {
|
||||
// Finished with XML data, now we can write data out.
|
||||
File fileOut(flags["output"] + ".texture");
|
||||
if(fileOut.exists()) return 0;
|
||||
|
||||
// Load input file
|
||||
File in(flags["input"]);
|
||||
if(!in.open(FILE_MODE_READ)) return 1;
|
||||
|
||||
int w, h, channels;
|
||||
auto imageRaw = stbi_load_from_file(in.file, &w, &h, &channels, STBI_rgb_alpha);
|
||||
if(imageRaw == NULL) {
|
||||
std::cout << "Failed to load input texture!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
in.close();
|
||||
|
||||
// Convert to floating points
|
||||
size_t len = STBI_rgb_alpha * w * h;
|
||||
float_t *dataImage = (float_t*)malloc(sizeof(float) * len);
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
auto dataIn = imageRaw[i];
|
||||
dataImage[i] = ((float_t)dataIn) / 255.0f;
|
||||
}
|
||||
stbi_image_free(imageRaw);
|
||||
|
||||
// Open and create output
|
||||
File out(flags["output"] + ".texture");
|
||||
if(!out.mkdirp()) {
|
||||
std::cout << "Failed to make output dir " << out.filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if(!out.open(FILE_MODE_WRITE)) {
|
||||
std::cout << "Failed to open texture file for writing " << out.filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write info
|
||||
char headerBuffer[64];
|
||||
size_t headerBufferLength = sprintf((char *)headerBuffer, "%i|%i|", w, h);
|
||||
if(!out.writeRaw(headerBuffer, headerBufferLength)) {
|
||||
std::cout << "Failed to write texture header for " << out.filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write texture
|
||||
if(!out.writeRaw((char *)dataImage, sizeof(float_t) * len)) {
|
||||
std::cout << "Failed to write texture data for " << out.filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
free(dataImage);
|
||||
|
||||
return 0;
|
||||
}
|
19
archive/tools/tools/texturegen/TextureGen.hpp
Normal file
19
archive/tools/tools/texturegen/TextureGen.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "util/DawnTool.hpp"
|
||||
#include "util/File.hpp"
|
||||
#include "../../util/image.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
class TextureGen : public DawnTool {
|
||||
protected:
|
||||
std::vector<std::string> getRequiredFlags() override;
|
||||
|
||||
public:
|
||||
int32_t start();
|
||||
};
|
||||
}
|
25
archive/tools/tools/tilesetgen/CMakeLists.txt
Normal file
25
archive/tools/tools/tilesetgen/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2021 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Texture Build Tool
|
||||
project(tilesetgen VERSION 1.0)
|
||||
add_executable(tilesetgen)
|
||||
target_sources(tilesetgen
|
||||
PRIVATE
|
||||
main.cpp
|
||||
../../util/file.cpp
|
||||
../../util/image.cpp
|
||||
)
|
||||
target_include_directories(tilesetgen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
target_link_libraries(tilesetgen
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
stb
|
||||
)
|
147
archive/tools/tools/tilesetgen/main.cpp
Normal file
147
archive/tools/tools/tilesetgen/main.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dawnsharedlibs.hpp"
|
||||
#include "../../util/file.hpp"
|
||||
#include "../../util/image.hpp"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *in;
|
||||
char *out;
|
||||
FILE *file;
|
||||
char path[FILENAME_MAX + 1];
|
||||
int w, h, channels, cols, rows;
|
||||
stbi_uc *dataImageRaw;
|
||||
int gapX, gapY, borderX, borderY;
|
||||
|
||||
if(argc < 5) {
|
||||
printf("Invalid number of arguments");
|
||||
return 1;
|
||||
}
|
||||
|
||||
in = argv[1];
|
||||
out = argv[2];
|
||||
gapX = 0, gapY = 0, borderX = 0, borderY = 0;
|
||||
|
||||
cols = atoi(argv[3]);
|
||||
if(cols <= 0) {
|
||||
printf("Columns is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
rows = atoi(argv[4]);
|
||||
if(rows <= 0) {
|
||||
printf("Rows is invalid");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(argc >= 6) {
|
||||
gapX = atoi(argv[5]);
|
||||
if(gapX <= 0) {
|
||||
printf("Gap X is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(argc >= 7) {
|
||||
gapY = atoi(argv[6]);
|
||||
if(gapY <= 0) {
|
||||
printf("Gap Y is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(argc >= 8) {
|
||||
borderX = atoi(argv[7]);
|
||||
if(borderX <= 0) {
|
||||
printf("Border X is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(argc >= 9) {
|
||||
borderY = atoi(argv[8]);
|
||||
if(borderY <= 0) {
|
||||
printf("Border Y is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize slashes
|
||||
fileNormalizeSlashes(in);
|
||||
fileNormalizeSlashes(out);
|
||||
|
||||
// Check the output doesn't already exist
|
||||
sprintf(path, "%s.tileset", out);
|
||||
file = fopen(path, "rb");
|
||||
if(file != NULL) {
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read in the original texture
|
||||
file = fopen(in, "rb");
|
||||
if(file == NULL) {
|
||||
printf("Failed to open file!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read image data
|
||||
dataImageRaw = stbi_load_from_file(file, &w, &h, &channels, STBI_rgb_alpha);
|
||||
if(dataImageRaw == NULL) {
|
||||
printf("Failed to load input texture!\n");
|
||||
return 1;
|
||||
}
|
||||
fclose(file);
|
||||
free(dataImageRaw);
|
||||
|
||||
if(w <= 0 || h <= 0) {
|
||||
printf("Reading image failed (corrupted?)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Calculate division sizes (pixels)
|
||||
int divX = (w - (borderX * 2) - (gapX * (cols - 1))) / cols;
|
||||
int divY = (h - (borderY * 2) - (gapY * (rows - 1))) / rows;
|
||||
|
||||
// Calculate the division sizes (units)
|
||||
float tdivX = (float)divX / (float)w;
|
||||
float tdivY = (float)divY / (float)h;
|
||||
|
||||
// Output buffer prep
|
||||
char *buffer = (char *)malloc(sizeof(char) * (cols * rows * 48 + 48 + 48));
|
||||
buffer[0] = '\0';
|
||||
|
||||
sprintf(buffer, "%i|%i|%i|%i|", cols, rows, divX, divY);
|
||||
|
||||
// Now prep tileset.
|
||||
for(float y = 0; y < rows; y++) {
|
||||
for(float x = 0; x < cols; x++) {
|
||||
float ux0 = ((float)borderX + ((float)divX * x) + ((float)gapX * x)) / (float)w;
|
||||
float ux1 = ux0 + tdivX;
|
||||
float uy0 = ((float)borderY + ((float)divY * y) + ((float)gapY * y)) / (float)h;
|
||||
float uy1 = uy0 + tdivY;
|
||||
sprintf(buffer, "%s%f,%f,%f,%f|", buffer, ux0, ux1, uy0, uy1);
|
||||
}
|
||||
}
|
||||
|
||||
// Open output file
|
||||
fileMkdirp(path);
|
||||
file = fopen(path, "wb");
|
||||
if(file == NULL) {
|
||||
free(buffer);
|
||||
printf("Invalid tileset file out!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write and close
|
||||
fwrite(buffer, sizeof(char), strlen(buffer), file);
|
||||
fclose(file);
|
||||
free(buffer);
|
||||
|
||||
return 0;
|
||||
}
|
25
archive/tools/tools/truetypegen/CMakeLists.txt
Normal file
25
archive/tools/tools/truetypegen/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2021 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Texture Build Tool
|
||||
project(truetypegen VERSION 1.0)
|
||||
add_executable(truetypegen)
|
||||
target_sources(truetypegen
|
||||
PRIVATE
|
||||
main.cpp
|
||||
../../util/file.cpp
|
||||
../../util/image.cpp
|
||||
)
|
||||
target_include_directories(truetypegen
|
||||
PUBLIC
|
||||
${DAWN_SHARED_INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
target_link_libraries(truetypegen
|
||||
PUBLIC
|
||||
${DAWN_BUILD_HOST_LIBS}
|
||||
stb
|
||||
)
|
141
archive/tools/tools/truetypegen/main.cpp
Normal file
141
archive/tools/tools/truetypegen/main.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dawnsharedlibs.hpp"
|
||||
#include "../../util/file.hpp"
|
||||
#include "../../util/image.hpp"
|
||||
#ifndef STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb_truetype.h>
|
||||
#endif
|
||||
|
||||
#define TRUETYPE_FIRST_CHAR 32
|
||||
#define TRUETYPE_NUM_CHARS 96
|
||||
|
||||
int main(int argc, char *args[]) {
|
||||
FILE *file;
|
||||
char path[FILENAME_MAX + 1];
|
||||
int32_t width, height, fontSize, textureSize;
|
||||
|
||||
/*
|
||||
args0 - PATH
|
||||
args1 - Input Filename (TTF file)
|
||||
args2 - Output Filename
|
||||
args3 - Width of the output texture (Resolution X)
|
||||
args4 - Height of the output texture (Resolution Y)
|
||||
args5 - Font size to draw
|
||||
*/
|
||||
|
||||
if(argc != 6) {
|
||||
printf("Invalid number of arguments\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *fileIn = args[1];
|
||||
char *fileOut = args[2];
|
||||
char *strWidth = args[3];
|
||||
char *strHeight = args[4];
|
||||
char *strFontSize = args[5];
|
||||
|
||||
// Normalize slashes
|
||||
fileNormalizeSlashes(fileIn);
|
||||
fileNormalizeSlashes(fileOut);
|
||||
|
||||
// Check the output doesn't already exist
|
||||
sprintf(path, "%s.truetype", fileOut);
|
||||
file = fopen(path, "rb");
|
||||
if(file != NULL) {
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
width = atoi(strWidth);
|
||||
if(width <= 0) {
|
||||
printf("Width is invalid.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
height = atoi(strHeight);
|
||||
if(height <= 0) {
|
||||
printf("Height is invalid.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fontSize = atoi(strFontSize);
|
||||
if(fontSize <= 0) {
|
||||
printf("Font size is invalid.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read in the TTF data
|
||||
file = fopen(fileIn, "rb");
|
||||
if(file == NULL) {
|
||||
printf("Failed to open input TTF file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Seek to end, get length, seek back to start.
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
// Read in all data
|
||||
char *ttfData = (char*)malloc(sizeof(char) * fileSize);
|
||||
size_t readSize = fread(ttfData, 1, fileSize, file);
|
||||
fclose(file);
|
||||
if(readSize < fileSize) {
|
||||
free(ttfData);
|
||||
printf("Failed to read all data form TTF\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create bitmap data. This is a single channel color (alpha).
|
||||
textureSize = width * height;
|
||||
stbi_uc *bitmapData = (stbi_uc*)malloc(sizeof(stbi_uc) * textureSize);
|
||||
stbtt_bakedchar characterData[TRUETYPE_NUM_CHARS];
|
||||
|
||||
// Now parse the TTF itself.
|
||||
stbtt_BakeFontBitmap(
|
||||
(uint8_t*)ttfData, 0, (float)fontSize, bitmapData,
|
||||
width, height,
|
||||
TRUETYPE_FIRST_CHAR, TRUETYPE_NUM_CHARS,
|
||||
characterData
|
||||
);
|
||||
|
||||
// Prepare output file for writing.
|
||||
sprintf(path, "%s.truetype", fileOut);
|
||||
fileMkdirp(path);
|
||||
file = fopen(path, "wb");
|
||||
if(file == NULL) {
|
||||
printf("Failed to create output TTF file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Now prepare output data.
|
||||
char headerBuffer[64];
|
||||
int32_t headerBufferLength = sprintf(
|
||||
headerBuffer, "%i|%i|%i|", width, height, fontSize
|
||||
);
|
||||
fwrite(headerBuffer, sizeof(char), headerBufferLength, file);
|
||||
|
||||
// Write output pixels.
|
||||
float outputPixels[4];
|
||||
for(int32_t i = 0; i < textureSize; i++) {
|
||||
outputPixels[0] = 1.0f;
|
||||
outputPixels[1] = 1.0f;
|
||||
outputPixels[2] = 1.0f;
|
||||
outputPixels[3] = ((float)bitmapData[i]) / 255.0f;
|
||||
fwrite(outputPixels, sizeof(float), 4, file);
|
||||
}
|
||||
|
||||
// Now write output quads data.
|
||||
fwrite(characterData, sizeof(stbtt_bakedchar), TRUETYPE_NUM_CHARS, file);
|
||||
fclose(file);
|
||||
free(bitmapData);
|
||||
|
||||
return 0;
|
||||
}
|
17
archive/tools/util/CMakeLists.txt
Normal file
17
archive/tools/util/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2023 Dominic Msters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
set(D ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
set(
|
||||
DAWN_TOOL_SOURCES
|
||||
${D}/DawnTool.cpp
|
||||
${D}/File.cpp
|
||||
${D}/Language.cpp
|
||||
${D}/CodeGen.cpp
|
||||
|
||||
CACHE INTERNAL
|
||||
${DAWN_CACHE_TARGET}
|
||||
)
|
120
archive/tools/util/csv.cpp
Normal file
120
archive/tools/util/csv.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "csv.hpp"
|
||||
|
||||
char * csvGetCell(csv_t *csv, int32_t row, int32_t cell) {
|
||||
return csv->rows[(row * CSV_COLUMN_COUNT_MAX) + cell];
|
||||
}
|
||||
|
||||
void csvParse(char *string, csv_t *csv) {
|
||||
char c;
|
||||
size_t i, j, length;
|
||||
csvparsestate_t state;
|
||||
int32_t rowCellCount;
|
||||
|
||||
length = strlen(string);
|
||||
csv->buffer = (char *)malloc(sizeof(char) * (length+1) * CSV_COLUMN_COUNT_MAX * CSV_ROW_COUNT_MAX);
|
||||
csv->cellCounts = (int32_t *)malloc(sizeof(int32_t) * CSV_ROW_COUNT_MAX);
|
||||
csv->rows = (char**)malloc(sizeof(char*) * CSV_ROW_COUNT_MAX * CSV_COLUMN_COUNT_MAX);
|
||||
|
||||
i = 0;
|
||||
j = 0;
|
||||
rowCellCount = 0;
|
||||
csv->rowCount = 0;
|
||||
state = CSV_PARSE_STATE_FIND_CELL;
|
||||
while(i < length) {
|
||||
c = string[i++];
|
||||
|
||||
// What are we doing
|
||||
switch(state) {
|
||||
case CSV_PARSE_STATE_FIND_CELL:
|
||||
if(c == '"') {
|
||||
state = CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES;
|
||||
csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
|
||||
rowCellCount++;
|
||||
continue;
|
||||
} else if(c == '\r' || c == '\n') {
|
||||
// Newline (todo: is this a blank line?)
|
||||
state = CSV_PARSE_STATE_LINE_END;
|
||||
continue;
|
||||
} else if(c == ',') {
|
||||
csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
|
||||
csv->buffer[j++] = '\0';
|
||||
rowCellCount++;
|
||||
continue;
|
||||
} else {
|
||||
state = CSV_PARSE_STATE_PARSE_CELL;
|
||||
csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
|
||||
csv->buffer[j++] = c;
|
||||
rowCellCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
case CSV_PARSE_STATE_PARSE_CELL:
|
||||
if(c == '\r' || c == '\n') {
|
||||
state = CSV_PARSE_STATE_LINE_END;
|
||||
csv->buffer[j++] = '\0';
|
||||
continue;
|
||||
} else if(c == ',') {
|
||||
state = CSV_PARSE_STATE_FIND_CELL;
|
||||
csv->buffer[j++] = '\0';
|
||||
continue;
|
||||
}
|
||||
csv->buffer[j++] = c;
|
||||
continue;
|
||||
|
||||
case CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES:
|
||||
if((c == '\\' && string[i] == '"') || (c == '"' && string[i] == '"')) {
|
||||
// Handle escaped quotes. I normally see [\"] but excel does [""] in
|
||||
// most cases
|
||||
csv->buffer[j++] = '"';
|
||||
i++;
|
||||
continue;
|
||||
} else if(c == '"') {
|
||||
// Handle end of quoted string
|
||||
state = CSV_PARSE_STATE_FIND_CELL;
|
||||
csv->buffer[j++] = '\0';
|
||||
// Because we tend to do [",] at the end of a quoted cell, we do this
|
||||
// to prevent [,,] cases being treated the same
|
||||
if(string[i] == ',') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normal character.
|
||||
csv->buffer[j++] = c;
|
||||
continue;
|
||||
|
||||
case CSV_PARSE_STATE_LINE_END:
|
||||
// Skip blanks
|
||||
if(c == '\r' || c == '\n') continue;
|
||||
csv->cellCounts[csv->rowCount] = rowCellCount;
|
||||
csv->rowCount++;
|
||||
rowCellCount = 0;
|
||||
state = CSV_PARSE_STATE_FIND_CELL;
|
||||
i--;
|
||||
continue;
|
||||
|
||||
default:
|
||||
printf("Error occured during parse operation.");
|
||||
free(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
csv->buffer[j++] = '\0';
|
||||
|
||||
if(rowCellCount != 0) {
|
||||
csv->cellCounts[csv->rowCount] = rowCellCount;
|
||||
csv->rowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void csvDispose(csv_t *csv) {
|
||||
free(csv->buffer);
|
||||
free(csv->cellCounts);
|
||||
free(csv->rows);
|
||||
}
|
33
archive/tools/util/csv.hpp
Normal file
33
archive/tools/util/csv.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dawnsharedlibs.hpp"
|
||||
#include "string.h"
|
||||
|
||||
#define CSV_ROW_COUNT_MAX 128
|
||||
#define CSV_COLUMN_COUNT_MAX 16
|
||||
|
||||
typedef enum {
|
||||
CSV_PARSE_STATE_FIND_CELL,//0
|
||||
CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES,
|
||||
CSV_PARSE_STATE_PARSE_CELL,//2
|
||||
CSV_PARSE_STATE_LINE_END
|
||||
} csvparsestate_t;
|
||||
|
||||
typedef struct {
|
||||
char *buffer;
|
||||
char **rows;
|
||||
int32_t rowCount;
|
||||
int32_t *cellCounts;
|
||||
} csv_t;
|
||||
|
||||
char * csvGetCell(csv_t *csv, int32_t row, int32_t cell);
|
||||
|
||||
void csvParse(char *string, csv_t *csv);
|
||||
|
||||
void csvDispose(csv_t *csv);
|
55
archive/tools/util/image.cpp
Normal file
55
archive/tools/util/image.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "image.hpp"
|
||||
|
||||
#ifndef STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#endif
|
||||
|
||||
#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb_image_resize.h>
|
||||
#endif
|
||||
|
||||
#ifndef STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
#endif
|
||||
|
||||
void imageCopy(
|
||||
uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
|
||||
uint8_t *dest, int32_t destWidth, int32_t destHeight,
|
||||
int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
|
||||
int32_t pasteX, int32_t pasteY,
|
||||
int32_t channels
|
||||
) {
|
||||
int32_t x, y, c;
|
||||
int32_t absX, absY;
|
||||
int32_t sourceIndex, targetIndex;
|
||||
|
||||
if(cropX == -1) cropX = 0;
|
||||
if(cropY == -1) cropY = 0;
|
||||
if(cropWidth == -1) cropWidth = sourceWidth;
|
||||
if(cropHeight == -1) cropHeight = sourceHeight;
|
||||
if(pasteX == -1) pasteX = 0;
|
||||
if(pasteY == -1) pasteY = 0;
|
||||
|
||||
for(x = cropX; x < cropX + cropWidth; x++) {
|
||||
for(y = cropY; y < cropY + cropHeight; y++) {
|
||||
absX = x - cropX + pasteX;
|
||||
absY = y - cropY + pasteY;
|
||||
if(absX >= destWidth || absY >= destHeight || absX < 0 || absY < 0)continue;
|
||||
targetIndex = absY * destWidth + absX;
|
||||
sourceIndex = y * sourceWidth + x;
|
||||
for(c = 0; c < channels; c++) {
|
||||
dest[(targetIndex*channels) + c] = source[(sourceIndex*channels) + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
archive/tools/util/image.hpp
Normal file
39
archive/tools/util/image.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dawnsharedlibs.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_resize.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
/**
|
||||
* Unused currently.
|
||||
*
|
||||
* @param source
|
||||
* @param sourceWidth
|
||||
* @param sourceHeight
|
||||
* @param dest
|
||||
* @param destWidth
|
||||
* @param destHeight
|
||||
* @param cropX
|
||||
* @param cropY
|
||||
* @param cropWidth
|
||||
* @param cropHeight
|
||||
* @param pasteX
|
||||
* @param pasteY
|
||||
* @param channels
|
||||
*/
|
||||
void imageCopy(
|
||||
uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
|
||||
uint8_t *dest, int32_t destWidth, int32_t destHeight,
|
||||
int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
|
||||
int32_t pasteX, int32_t pasteY,
|
||||
int32_t channels
|
||||
);
|
36
archive/tools/util/string.hpp
Normal file
36
archive/tools/util/string.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dawnsharedlibs.hpp"
|
||||
|
||||
/**
|
||||
* Remove all instances of a character from a C-Styled string.
|
||||
*
|
||||
* @param string String to remove characters from.
|
||||
* @param remove Character to remove.
|
||||
*/
|
||||
static inline void stringRemoveAll(char *string, char remove) {
|
||||
size_t len = strlen(string);
|
||||
size_t i, j;
|
||||
|
||||
i = 0;
|
||||
while(i < len) {
|
||||
char c = string[i];
|
||||
if(c != remove) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
j = i + 1;
|
||||
while(j < len) {
|
||||
string[j-1] = string[j];
|
||||
j++;
|
||||
}
|
||||
len--;
|
||||
}
|
||||
}
|
231
archive/tools/util/xml.cpp
Normal file
231
archive/tools/util/xml.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "xml.hpp"
|
||||
|
||||
int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) {
|
||||
char c;
|
||||
int32_t level = 0;
|
||||
uint8_t doing = XML_DOING_NOTHING;
|
||||
uint8_t doingBeforeComment;
|
||||
bool insideTag = false;
|
||||
char* buffer = (char *)malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
||||
int32_t bufferLength = 0;
|
||||
|
||||
xml->value = NULL;
|
||||
xml->attributeCount = 0;
|
||||
|
||||
xml->children = (xml_t *)malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX);
|
||||
xml->childrenCount = 0;
|
||||
|
||||
while(c = data[i++]) {
|
||||
switch(doing) {
|
||||
case XML_DOING_NOTHING:
|
||||
// Look for either an opening tag (<) or a word for a value.
|
||||
if(c == '>') continue;
|
||||
if(c == '<') {
|
||||
if(data[i] == '!' && data[i+1] == '-' && data[i+2] == '-') {
|
||||
doingBeforeComment = doing;
|
||||
doing = XML_PARSING_COMMENT;
|
||||
i += 3;
|
||||
} else if(insideTag) {
|
||||
i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
|
||||
doing = XML_PARSING_CHILD;
|
||||
} else {
|
||||
doing = XML_PARSING_TAG_NAME;
|
||||
level++;
|
||||
insideTag = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(xmlIsWhitespace(c)) continue;
|
||||
doing = XML_PARSING_VALUE;
|
||||
buffer[bufferLength++] = c;
|
||||
break;
|
||||
|
||||
case XML_PARSING_TAG_NAME:
|
||||
// Just keep reading until we either hit a space (end of the tag name)
|
||||
// or a closing tag value, either / or >
|
||||
if(xmlIsWhitespace(c) || c == '>' || c == '/') {
|
||||
buffer[bufferLength] = '\0';
|
||||
xml->node = buffer;
|
||||
buffer = (char *)malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
||||
bufferLength = 0;
|
||||
if(c == '/') {
|
||||
level--;
|
||||
insideTag = false;
|
||||
doing = XML_PARSING_CLOSE;
|
||||
} else {
|
||||
doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
buffer[bufferLength++] = c;
|
||||
break;
|
||||
|
||||
case XML_LOOKING_FOR_ATTRIBUTE:
|
||||
// Look until we hit either the end of a tag, or the attribute itself
|
||||
if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') {
|
||||
if(c == '>' || c == '/') {
|
||||
doing = XML_DOING_NOTHING;
|
||||
if(c == '/') {
|
||||
level--;
|
||||
insideTag = false;
|
||||
doing = XML_PARSING_CLOSE;
|
||||
}
|
||||
} else if(c == '=') {
|
||||
doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE;
|
||||
} else {
|
||||
doing = XML_LOOKING_FOR_ATTRIBUTE;
|
||||
}
|
||||
|
||||
if(bufferLength > 0) {
|
||||
buffer[bufferLength] = '\0';
|
||||
xml->attributeNames[xml->attributeCount++] = buffer;
|
||||
xml->attributeDatas[xml->attributeCount] = NULL;
|
||||
buffer = (char *)malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
||||
bufferLength = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[bufferLength++] = c;
|
||||
break;
|
||||
|
||||
case XML_LOOKING_FOR_ATTRIBUTE_VALUE:
|
||||
// Keep looking until we find a quote mark
|
||||
if(xmlIsWhitespace(c)) continue;
|
||||
if(c == '>' || c == '/') {
|
||||
doing = XML_DOING_NOTHING;
|
||||
insideTag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c != '"') continue;
|
||||
doing = XML_PARSING_ATTRIBUTE_VALUE;
|
||||
break;
|
||||
|
||||
case XML_PARSING_ATTRIBUTE_VALUE:
|
||||
// Parse the attribute value until we find a quote mark.
|
||||
if(c == '"') {
|
||||
doing = XML_LOOKING_FOR_ATTRIBUTE;
|
||||
buffer[bufferLength] = '\0';
|
||||
xml->attributeDatas[xml->attributeCount - 1] = buffer;
|
||||
buffer = (char *)malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
||||
bufferLength = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[bufferLength++] = c;
|
||||
break;
|
||||
|
||||
case XML_PARSING_VALUE:
|
||||
// Keep parsing child until we find a < for an opening/closing tag.
|
||||
if(c == '<') {
|
||||
// In HTML Spec there could be a child here but not in XML spec.
|
||||
doing = XML_PARSING_CLOSE;
|
||||
buffer[bufferLength] = '\0';
|
||||
bufferLength = 0;
|
||||
xml->value = buffer;
|
||||
buffer = (char *)malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[bufferLength++] = c;
|
||||
break;
|
||||
|
||||
case XML_PARSING_CHILD:
|
||||
if(c == '<') {
|
||||
// Read ahead and confirm this is a close or not
|
||||
if(data[i] == '/') {
|
||||
doing = XML_PARSING_CLOSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(data[i] == '!' && data[i+1] == '-' && data[i+2] == '-') {
|
||||
doingBeforeComment = doing;
|
||||
doing = XML_PARSING_COMMENT;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Likely another child.
|
||||
i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
|
||||
}
|
||||
|
||||
if(xmlIsWhitespace(c)) continue;
|
||||
|
||||
// In HTML Spec there's a chance for there to be a value here, but not
|
||||
// in the XML spec.
|
||||
break;
|
||||
|
||||
case XML_PARSING_CLOSE:
|
||||
// Just keep parsing until the tag closer finishes.
|
||||
if(c != '>') continue;
|
||||
doing = XML_DOING_NOTHING;
|
||||
|
||||
//TODO: Return index or something?
|
||||
free(buffer);
|
||||
return i;
|
||||
|
||||
case XML_PARSING_COMMENT:
|
||||
if(c != '-') continue;
|
||||
if(data[i] != '-') continue;
|
||||
if(data[i+1] != '>') continue;
|
||||
i += 2;
|
||||
doing = doingBeforeComment;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
return i;
|
||||
}
|
||||
|
||||
void xmlLoad(xml_t *xml, char *data) {
|
||||
xmlLoadChild(xml, data, 0);
|
||||
}
|
||||
|
||||
void xmlDispose(xml_t *xml) {
|
||||
uint8_t i;
|
||||
|
||||
// Dispose children recursively
|
||||
for(i = 0; i < xml->childrenCount; i++) {
|
||||
xmlDispose(xml->children + i);
|
||||
}
|
||||
|
||||
// Free children array.
|
||||
free(xml->children);
|
||||
|
||||
// Dispose attributes
|
||||
for(i = 0; i < xml->attributeCount; i++) {
|
||||
free(xml->attributeNames[i]);
|
||||
if((xml->attributeDatas + i) != NULL) {
|
||||
free(xml->attributeDatas[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(xml->node);
|
||||
if(xml-> value != NULL) free(xml->value);
|
||||
}
|
||||
|
||||
int16_t xmlGetAttributeByName(xml_t *xml, char *name) {
|
||||
int16_t i;
|
||||
for(i = 0; i < xml->attributeCount; i++) {
|
||||
if(strcmp(xml->attributeNames[i], name) == 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool xmlIsWhitespace(char c) {
|
||||
return c == ' ' || c == '\r' || c == '\n' || c == '\t';
|
||||
}
|
81
archive/tools/util/xml.hpp
Normal file
81
archive/tools/util/xml.hpp
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dawnsharedlibs.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
#define XML_DOING_NOTHING 0x00
|
||||
#define XML_PARSING_TAG_NAME 0x01
|
||||
#define XML_LOOKING_FOR_ATTRIBUTE 0x02
|
||||
#define XML_PARSING_ATTRIBUTE_NAME 0x03
|
||||
#define XML_LOOKING_FOR_ATTRIBUTE_VALUE 0x04
|
||||
#define XML_PARSING_ATTRIBUTE_VALUE 0x05
|
||||
#define XML_PARSING_VALUE 0x06
|
||||
#define XML_PARSING_CHILD 0x07
|
||||
#define XML_PARSING_CLOSE 0x08
|
||||
#define XML_PARSING_COMMENT 0x09
|
||||
|
||||
#define XML_TEXT_BUFFER_MAX 2048
|
||||
#define XML_CHILD_COUNT_MAX 128
|
||||
#define XML_ATTRIBUTE_MAX 128
|
||||
|
||||
typedef struct _xml_t xml_t;
|
||||
|
||||
typedef struct _xml_t {
|
||||
char *node;
|
||||
char *value;
|
||||
|
||||
char *attributeNames[XML_ATTRIBUTE_MAX];
|
||||
char *attributeDatas[XML_ATTRIBUTE_MAX];
|
||||
uint8_t attributeCount;
|
||||
|
||||
xml_t *children;
|
||||
uint8_t childrenCount;
|
||||
} xml_t;
|
||||
|
||||
/**
|
||||
* Load an XML child from a string buffer.
|
||||
*
|
||||
* @param xml XML to load.
|
||||
* @param data Data to parse
|
||||
* @param i Character index within the data
|
||||
* @return The index in the data string this XML node ends.
|
||||
*/
|
||||
int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i);
|
||||
|
||||
/**
|
||||
* Load an XML String into an XML memory.
|
||||
*
|
||||
* @param xml XML to load into.
|
||||
* @param data XML string.
|
||||
*/
|
||||
void xmlLoad(xml_t *xml, char *data);
|
||||
|
||||
/**
|
||||
* Dispose a previously loaded XML.
|
||||
*
|
||||
* @param xml XML to dispose.
|
||||
*/
|
||||
void xmlDispose(xml_t *xml);
|
||||
|
||||
/**
|
||||
* Find an attribute index by its name.
|
||||
*
|
||||
* @param xml Xml node to get the attribute from.
|
||||
* @param name The name of the attribute you're trying to find.
|
||||
* @return -1 if not found, otherwise the index of the attribute within array.
|
||||
*/
|
||||
int16_t xmlGetAttributeByName(xml_t *xml, char *name);
|
||||
|
||||
/**
|
||||
* Checks if a given character is a whitespace character or not.
|
||||
*
|
||||
* @param c Character to check if a whitespace.
|
||||
* @return True if whitespace, otherwise false.
|
||||
*/
|
||||
bool xmlIsWhitespace(char c);
|
Reference in New Issue
Block a user