diff --git a/.gitignore b/.gitignore index b376228..87f3449 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,10 @@ compile_commands.json /config.py /config_*.py +# Generated files +*.gen.* +!*.gen.*.jinja + # Prerequisites *.d diff --git a/SConstruct b/SConstruct index ced104c..90642c4 100644 --- a/SConstruct +++ b/SConstruct @@ -1,10 +1,13 @@ config = { 'PROJECT_NAME': 'RAID Framework', - 'CXX_NO_EXCEPTIONS': True + 'TOOLS': ['jinja'] } env = SConscript('external/scons-plus-plus/SConscript', exports = ['config']) env.Append(CPPPATH = [Dir('private'), Dir('public')]) +# add the default recipe repository +env.RecipeRepo('mewin-stable', 'https://git.mewin.de/mewin/spp_recipes.git', 'stable') + # library env = env.Module('private/raid/SModule') diff --git a/external/scons-plus-plus b/external/scons-plus-plus index c994752..2769fd8 160000 --- a/external/scons-plus-plus +++ b/external/scons-plus-plus @@ -1 +1 @@ -Subproject commit c994752c3244fdf9835b1d3a5238094d2a799855 +Subproject commit 2769fd801fa456d809e9e7c8bf2d1be4953a734d diff --git a/private/raid/SModule b/private/raid/SModule index 254ed18..3f09d28 100644 --- a/private/raid/SModule +++ b/private/raid/SModule @@ -3,11 +3,18 @@ import json Import('env') +if not hasattr(env, 'Jinja'): + env.Error('RAID requires the Jinja tool.') + src_files = Split(""" application.cpp + fonts.gen.cpp stb_image.cpp """) +env.Jinja("fonts.gen.cpp") +env.Jinja("fonts.gen.hpp") + with open('../../dependencies.json', 'r') as f: dependencies = env.DepsFromJson(json.load(f)) diff --git a/private/raid/application.cpp b/private/raid/application.cpp index 8469916..f7cfc8c 100644 --- a/private/raid/application.cpp +++ b/private/raid/application.cpp @@ -16,6 +16,7 @@ #include #include #include +#include "./fonts.gen.hpp" namespace raid { @@ -242,7 +243,7 @@ void Application::configureImgui() std::vector Application::getDefaultFonts() { return {{ - .path = "/data/fonts/NotoSans-Regular.ttf" + .path = DEFAULT_FONT_PATH }}; } @@ -295,6 +296,9 @@ bool Application::init() ); }; + mMemoryFS = mFS.emplaceAdapter(); + mMemoryFS->addFile(DEFAULT_FONT_PATH, NOTO_SANS_DATA); + addConfigDir(mijin::getKnownFolder(mijin::KnownFolder::USER_CONFIG_ROOT) / getFolderName()); addDataDir(mijin::getKnownFolder(mijin::KnownFolder::USER_DATA_ROOT) / getFolderName()); diff --git a/private/raid/fonts.gen.cpp.jinja b/private/raid/fonts.gen.cpp.jinja new file mode 100644 index 0000000..1aee6a9 --- /dev/null +++ b/private/raid/fonts.gen.cpp.jinja @@ -0,0 +1,7 @@ + +#include "./fonts.gen.hpp" + +namespace raid +{ +extern const std::array NOTO_SANS_DATA = { {{ file_content_hex('#res/fonts/NotoSans-Regular.ttf') }} }; +} \ No newline at end of file diff --git a/private/raid/fonts.gen.hpp.jinja b/private/raid/fonts.gen.hpp.jinja new file mode 100644 index 0000000..8ce74d7 --- /dev/null +++ b/private/raid/fonts.gen.hpp.jinja @@ -0,0 +1,16 @@ + +#pragma once + +#if !defined(RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED) +#define RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED 1 + +#include +#include + +namespace raid +{ +inline constexpr std::size_t NOTO_SANS_SIZE = {{ file_size('#res/fonts/NotoSans-Regular.ttf' )}}; +extern const std::array NOTO_SANS_DATA; +} // namespace raid + +#endif // !defined(RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED) diff --git a/public/raid/raid.hpp b/public/raid/raid.hpp index e0a2f44..507f878 100644 --- a/public/raid/raid.hpp +++ b/public/raid/raid.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ inline constexpr ImGuiWindowFlags DEFAULT_MAIN_WINDOW_FLAGS = 0 | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNav; +inline constexpr const char* DEFAULT_FONT_PATH = "/data/fonts/NotoSans-Regular.ttf"; enum class MessageSeverity : unsigned char { @@ -55,6 +57,7 @@ private: SDL_GLContext mGLContext = nullptr; mijin::StackedFileSystemAdapter mFS; + mijin::MemoryFileSystemAdapter* mMemoryFS = nullptr; mijin::SimpleTaskLoop mTaskLoop; std::unordered_map mTextures; @@ -91,6 +94,13 @@ public: [[nodiscard]] mijin::StackedFileSystemAdapter& getFS() { return mFS; } + [[nodiscard]] + mijin::MemoryFileSystemAdapter& getMemoryFS() + { + MIJIN_ASSERT_FATAL(mMemoryFS != nullptr, "Memory FS has not been initialized yet."); + return *mMemoryFS; + } + [[nodiscard]] mijin::SimpleTaskLoop& getLoop() { return mTaskLoop; } diff --git a/data/data/fonts/NotoSans-Regular.ttf b/res/fonts/NotoSans-Regular.ttf similarity index 100% rename from data/data/fonts/NotoSans-Regular.ttf rename to res/fonts/NotoSans-Regular.ttf diff --git a/data/data/fonts/OFL.txt b/res/fonts/OFL.txt similarity index 97% rename from data/data/fonts/OFL.txt rename to res/fonts/OFL.txt index 0e6dea2..e34fed1 100644 --- a/data/data/fonts/OFL.txt +++ b/res/fonts/OFL.txt @@ -1,93 +1,93 @@ -Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/site_scons/site_tools/jinja.py b/site_scons/site_tools/jinja.py new file mode 100644 index 0000000..b1871d1 --- /dev/null +++ b/site_scons/site_tools/jinja.py @@ -0,0 +1,194 @@ +# source: https://github.com/hgomersall/scons-jinja + +# 2025-03-28: Added JINJA_GLOBALS options. +# 2023-12-02: Added JINJA_FILTERS option. + +# +# Copyright (c) 2013 Henry Gomersall +# Copyright (c) 2025 Patrick Wuttke +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import SCons.Builder +import SCons.Tool +from SCons.Errors import StopError + +import jinja2 +from jinja2 import FileSystemLoader +from jinja2.utils import open_if_exists +from jinja2.exceptions import TemplateNotFound + +import os + +class FileSystemLoaderRecorder(FileSystemLoader): + ''' A wrapper around FileSystemLoader that records files as they are + loaded. These are contained within loaded_filenames set attribute. + ''' + def __init__(self, searchpath, encoding='utf-8'): + + self.loaded_filenames = set() + super(FileSystemLoaderRecorder, self).__init__(searchpath, encoding) + + def get_source(self, environment, template): + '''Overwritten FileSystemLoader.get_source method that extracts the + filename that is used to load each filename and adds it to + self.loaded_filenames. + ''' + for searchpath in self.searchpath: + filename = os.path.join(searchpath, template) + f = open_if_exists(filename) + if f is None: + continue + try: + contents = f.read().decode(self.encoding) + finally: + f.close() + + self.loaded_filenames.add(filename) + + return super(FileSystemLoaderRecorder, self).get_source( + environment, template) + + # If the template isn't found, then we have to drop out. + raise TemplateNotFound(template) + +def jinja_scanner(node, env, path): + + # Instantiate the file as necessary + node.get_text_contents() + + node_dir = os.path.dirname(str(node)) + + template_dir, filename = os.path.split(str(node)) + + template_search_path = ([template_dir] + + env.subst(env['JINJA_TEMPLATE_SEARCHPATH'])) + template_loader = FileSystemLoaderRecorder(template_search_path) + + jinja_env = jinja2.Environment(loader=template_loader, + extensions=['jinja2.ext.do'], **env['JINJA_ENVIRONMENT_VARS']) + jinja_env.filters.update(env['JINJA_FILTERS']) + jinja_env.globals.update(env['JINJA_GLOBALS']) + + try: + template = jinja_env.get_template(filename) + except TemplateNotFound as e: + raise StopError('Missing template: ' + + os.path.join(template_dir, str(e))) + + # We need to render the template to do all the necessary loading. + # + # It's necessary to respond to missing templates by grabbing + # the content as the exception is raised. This makes sure of the + # existence of the file upon which the current scanned node depends. + # + # I suspect that this is pretty inefficient, but it does + # work reliably. + context = env['JINJA_CONTEXT'] + + last_missing_file = '' + while True: + + try: + template.render(**context) + except TemplateNotFound as e: + if last_missing_file == str(e): + # We've already been round once for this file, + # so need to raise + raise StopError('Missing template: ' + + os.path.join(template_dir, str(e))) + + last_missing_file = str(e) + # Find where the template came from (using the same ordering + # as Jinja uses). + for searchpath in template_search_path: + filename = os.path.join(searchpath, last_missing_file) + if os.path.exists(filename): + continue + else: + env.File(filename).get_text_contents() + continue + + break + + # Get all the files that were loaded. The set includes the current node, + # so we remove that. + found_nodes_names = list(template_loader.loaded_filenames) + try: + found_nodes_names.remove(str(node)) + except ValueError as e: + raise StopError('Missing template node: ' + str(node)) + + return [env.File(f) for f in found_nodes_names] + +def render_jinja_template(target, source, env): + + output_str = '' + + if not source: + source = [f'{target}.jinja'] + + for template_file in source: + + template_dir, filename = os.path.split(str(template_file)) + + template_search_path = ([template_dir] + + env.subst(env['JINJA_TEMPLATE_SEARCHPATH'])) + template_loader = FileSystemLoaderRecorder(template_search_path) + + jinja_env = jinja2.Environment(loader=template_loader, + extensions=['jinja2.ext.do'], **env['JINJA_ENVIRONMENT_VARS']) + + jinja_env.filters.update(env['JINJA_FILTERS']) + jinja_env.globals.update(env['JINJA_GLOBALS']) + template = jinja_env.get_template(filename) + + context = env['JINJA_CONTEXT'] + template.render(**context) + + output_str += template.render(**context) + + with open(str(target[0]), 'w') as target_file: + target_file.write(output_str) + + return None + +def generate(env): + + env.SetDefault(JINJA_CONTEXT={}) + env.SetDefault(JINJA_ENVIRONMENT_VARS={}) + env.SetDefault(JINJA_FILTERS={}) + env.SetDefault(JINJA_GLOBALS={}) + env.SetDefault(JINJA_TEMPLATE_SEARCHPATH=[]) + + env['BUILDERS']['Jinja'] = SCons.Builder.Builder( + action=render_jinja_template) + + scanner = env.Scanner(function=jinja_scanner, + skeys=['.jinja']) + + env.Append(SCANNERS=scanner) + +def exists(env): + try: + import jinja2 + except ImportError as e: + raise StopError(ImportError, e.message)