From 4eb685d94e89a2aef61047225c1834a89de01de2 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Wed, 28 Oct 2020 13:13:57 +0200 Subject: Simplify rcc - drop qrc parsing and zstd compression --- .gitmodules | 3 - 3rd-party/tabler-icons | 1 - lib/compressionctx.h | 42 ------ lib/embed.h | 88 ------------ lib/zstd.cpp | 59 -------- meson.build | 46 +----- rcc | 54 +++---- rcc_format/__init__.py | 16 ++- rcc_format/generate.py | 9 -- rcc_format/util.py | 62 ++++---- rcc_format/zstd.py | 43 ------ readme.md | 3 +- test/main.cpp | 33 ----- test/resources.xrc | 377 ------------------------------------------------- 14 files changed, 66 insertions(+), 770 deletions(-) delete mode 100644 .gitmodules delete mode 160000 3rd-party/tabler-icons delete mode 100644 lib/compressionctx.h delete mode 100644 lib/embed.h delete mode 100644 lib/zstd.cpp delete mode 100755 rcc_format/generate.py delete mode 100644 rcc_format/zstd.py delete mode 100644 test/main.cpp delete mode 100644 test/resources.xrc diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e0954d7..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "3rd-party/tabler-icons"] - path = 3rd-party/tabler-icons - url = https://github.com/tabler/tabler-icons diff --git a/3rd-party/tabler-icons b/3rd-party/tabler-icons deleted file mode 160000 index ec114c0..0000000 --- a/3rd-party/tabler-icons +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ec114c0eb5bbfec35ce32ec7201be01cb01b24d5 diff --git a/lib/compressionctx.h b/lib/compressionctx.h deleted file mode 100644 index a07686f..0000000 --- a/lib/compressionctx.h +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include -#include - -#pragma once - -namespace embed -{ - -enum Compression { - None, - Zstd -}; - -class CompressionCtx -{ -public: - virtual ~CompressionCtx() = default; - [[nodiscard]] virtual std::vector decompress(const std::span &entry) const = 0; -}; - -class ZstdCompressionCtx final : public CompressionCtx -{ -public: - ZstdCompressionCtx(const std::span &dictionary); - ~ZstdCompressionCtx(); - [[nodiscard]] std::vector decompress(const std::span &entry) const override; -}; - -std::unique_ptr make_compression_ctx(const Compression algo, const std::span &dict) -{ - switch(algo) - { - case None: - return nullptr; - case Zstd: - return std::make_unique(dict); - } -} - -} // namespace embed diff --git a/lib/embed.h b/lib/embed.h deleted file mode 100644 index b57d830..0000000 --- a/lib/embed.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include -#include "compressionctx.h" - -namespace embed -{ - -template -class Resources -{ -public: - constexpr explicit Resources( - const std::array &entries, - const std::array, N> &values) - : m_entries(entries) - , m_values(values) - { - } - - [[nodiscard]] constexpr int id(const std::string_view &path) const - { - for(std::size_t i = 0; i < m_entries.size(); ++i) { - if(m_entries.at(i) == path) - return i; - } - return -1; - } - [[nodiscard]] constexpr std::span value(const int id_) const - { - if(id_ == -1) - return {}; - else - return m_values.at(id_); - } - [[nodiscard]] constexpr std::span value(const std::string_view &path) const - { - return value(id(path)); - } - - [[deprecated("With no compression, this function returns a copy; use ::value instead")]] - [[nodiscard]] std::vector decompress(const int id_) const - { - const auto v = value(id_); - return { v.begin(), v.end() }; - } - [[deprecated("With no compression, this function returns a copy; use ::value instead")]] - [[nodiscard]] std::vector decompress(const std::string_view &path) const - { - const auto v = value(path); - return { v.begin(), v.end() }; - } - -protected: - const std::array m_entries; - const std::array, N> m_values; -}; - -template -class CompressedResources final : public Resources -{ -public: - explicit CompressedResources( - const std::array &entries, - const std::array, N> &values, - const Compression algo, - const std::span &dictionary = {}) - : Resources(entries, values) - , m_compression(make_compression_ctx(algo, dictionary)) - { - } - ~CompressedResources() = default; - - [[nodiscard]] std::vector decompress(const int id_) const - { - return m_compression->decompress(Resources::value(id_)); - } - [[nodiscard]] std::vector decompress(const std::string_view &path) const - { - return m_compression->decompress(Resources::value(path)); - } - -protected: - const std::unique_ptr m_compression; -}; - -} // namespace embed diff --git a/lib/zstd.cpp b/lib/zstd.cpp deleted file mode 100644 index d2c27bc..0000000 --- a/lib/zstd.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "compressionctx.h" -#include -#include - -using namespace embed; - -ZSTD_DDict *dictPtr = nullptr; - -ZstdCompressionCtx::ZstdCompressionCtx(const std::span &dictionary) -{ - if(!dictionary.empty()) { - dictPtr = ZSTD_createDDict(dictionary.data(), dictionary.size()); - } -} - -ZstdCompressionCtx::~ZstdCompressionCtx() -{ - if(dictPtr != nullptr) { - ZSTD_freeDDict(dictPtr); - } -} - -[[nodiscard]] -std::vector ZstdCompressionCtx::decompress(const std::span &entry) const -{ - /* Read the content size from the frame header. For simplicity we require - * that it is always present. By default, zstd will write the content size - * in the header when it is known. If you can't guarantee that the frame - * content size is always written into the header, either use streaming - * decompression, or ZSTD_decompressBound(). - */ - unsigned long long const rSize = ZSTD_getFrameContentSize(entry.data(), entry.size()); - assert(rSize != ZSTD_CONTENTSIZE_ERROR); //, "%s: not compressed by zstd!", fname); - assert(rSize != ZSTD_CONTENTSIZE_UNKNOWN); //, "%s: original size unknown!", fname); - std::vector rBuff(rSize); - - /* Check that the dictionary ID matches. - * If a non-zstd dictionary is used, then both will be zero. - * By default zstd always writes the dictionary ID into the frame. - * Zstd will check if there is a dictionary ID mismatch as well. - */ - unsigned const expectedDictID = ZSTD_getDictID_fromDDict(dictPtr); - unsigned const actualDictID = ZSTD_getDictID_fromFrame(entry.data(), entry.size()); - assert(actualDictID == expectedDictID); //"DictID mismatch: expected %u got %u", - - /* Decompress using the dictionary. - * If you need to control the decompression parameters, then use the - * advanced API: ZSTD_DCtx_setParameter(), ZSTD_DCtx_refDDict(), and - * ZSTD_decompressDCtx(). - */ - ZSTD_DCtx *const dctx = ZSTD_createDCtx(); - assert(dctx != NULL); //, "ZSTD_createDCtx() failed!"); - size_t const dSize = ZSTD_decompress_usingDDict(dctx, rBuff.data(), rSize, entry.data(), entry.size(), dictPtr); - /* When zstd knows the content size, it will error if it doesn't match. */ - assert(dSize == rSize); //, "Impossible because zstd will check this condition!"); - - ZSTD_freeDCtx(dctx); - return rBuff; -} diff --git a/meson.build b/meson.build index dac9ca6..a4b0c6e 100644 --- a/meson.build +++ b/meson.build @@ -1,49 +1,13 @@ -project('libembed', ['cpp'], - version: '0.1', - default_options: ['cpp_std=c++2a', 'warning_level=3'], -) - -libzstd = dependency('libzstd') - -libembed_sourceset = import('sourceset').source_set() -libembed_sourceset.add(when: libzstd, if_true: [ libzstd, files('lib/zstd.cpp') ] ) -libembed_conf = libembed_sourceset.apply(configuration_data()) - -libembed = library('embed', libembed_conf.sources(), - dependencies: libembed_conf.dependencies() -) - -libembed_dep = declare_dependency( - link_with: libembed, - include_directories: include_directories('lib/') +project('rcc', [], + version: '0.1.0', + license: 'BSD-2-Clause', ) prog_python = import('python').find_installation('python3') - rcc = generator(prog_python, output: '@BASENAME@.h', arguments: [ meson.current_source_dir()/'rcc', '--output=@OUTPUT@', '@EXTRA_ARGS@', '@INPUT@' ] ) -if not meson.is_subproject() -resources_h = rcc.process(files('test/resources.xrc'), extra_args: [ '--namespace=staticdata', '-' ]) - -zstd_dictionary = custom_target('zstd_dictionary', - output: 'zstd_dict', - input: 'rcc', - command: [ prog_python, '@INPUT@', 'Zstd', '--train=@OUTPUT@', files('test/resources.xrc') ] -) - -zstd_resources_h = custom_target('zstd_resources.h', - output: 'zstd_resources.h', - input: [ 'rcc', zstd_dictionary ], - command: [prog_python, '@INPUT0@', '--namespace=zstd_data', '--output=@OUTPUT@', 'Zstd', '--dict=@INPUT1@', files('test/resources.xrc')], -) - -test('libembed', - executable('embed', - sources: [ 'test/main.cpp', resources_h, zstd_resources_h ], - dependencies: [ libembed_dep ] - ) -) -endif # meson.is_subproject() +# example usage: +#rcc.process(files('res/a.svg'), extra_args: [ '--namespace=svg', 'dump' ]) diff --git a/rcc b/rcc index e0b1ca3..3099ca6 100755 --- a/rcc +++ b/rcc @@ -4,48 +4,28 @@ import argparse import sys import rcc_format -def none(filelist, args): - rcc_format.write_header(args.output, args.namespace) - - for f in filelist: - with open(f.path, 'rb') as contents: - rcc_format.write_item(args.output, f.variable, contents.read()) - - rcc_format.write_entries(args.output, filelist) - print("constexpr auto compression = embed::None;", file=args.output) - - rcc_format.write_footer(args.output, args.namespace) - if __name__ == "__main__": parser = argparse.ArgumentParser( - description='Resource Compiler for C++', - epilog='For a full list of compression options, check {mode} --help.', + description='Simple Resource Compiler for C++20', + epilog='For more information check --help.', ) - mode = parser.add_subparsers(help='compression mode') - - none_mode = mode.add_parser('-') - none_mode.set_defaults(func=none) - - zstd_mode = mode.add_parser('Zstd', - description='use Zstd compression', - epilog='A dictionary is recommended if compressing many small files. size(source)/size(dictionary) should be >= 10' + mode = parser.add_subparsers(help='mode') + + dump_mode = mode.add_parser('dump', + formatter_class=argparse.RawDescriptionHelpFormatter, + description='Create a hexdump of [input](s) to [output]', + epilog='''The header file will also contain: + entries - an array of tuples of (input basename, span of data); + get - a get convience function that will return the span of a supplied basename +All of these are constexpr.''', ) - zstd_mode.add_argument('--binary', type=str, default='zstd', help='zstd binary name') - zstd_mode.add_argument('--train', type=argparse.FileType('wb'), help='train dictionary and exit') - zstd_mode.add_argument('-d', '--dict', type=argparse.FileType('rb'), help='use dictionary, recommended for many similar small files') - zstd_mode.add_argument('--dsize', type=int, default=512, help='dictionary size, used for training') - zstd_mode.add_argument('-l', '--level', type=int, default=19, help='compression level') - zstd_mode.set_defaults(func=rcc_format.zstd) - - gen_mode = mode.add_parser('generate', description='Generate .xrc file from inputs') - gen_mode.add_argument('-a', '--alias', type=str, help='set alias') - gen_mode.set_defaults(func=rcc_format.generate) - - parser.add_argument('input', type=argparse.FileType('rt'), help='input file (.xrc)') - parser.add_argument('-o', '--output', type=argparse.FileType('wt'), default=sys.stdout, help='output header file') - parser.add_argument('-n', '--namespace', type=str, default='resources', help='namespace') + dump_mode.set_defaults(func=rcc_format.dump) + + parser.add_argument('-o', '--output', type=argparse.FileType('wt'), default=sys.stdout, help='output header file (default: stdout)') + parser.add_argument('-n', '--namespace', type=str, default='resources', help='namespace (default: resources)') + parser.add_argument('input', type=argparse.FileType('rb'), nargs='+', help='input file(s)') args=parser.parse_args() - args.func(rcc_format.filelist(args.input), args) + args.func(args.input, args) diff --git a/rcc_format/__init__.py b/rcc_format/__init__.py index 48ebd70..629bc54 100644 --- a/rcc_format/__init__.py +++ b/rcc_format/__init__.py @@ -1,3 +1,15 @@ from rcc_format.util import * -from rcc_format.generate import generate -from rcc_format.zstd import zstd +from os.path import basename + +def dump(filelist, args): + write_header(args.output, args.namespace) + + items = [] + for f in filelist: + x = resource(basename(f.name), to_variable_name(f.name), f.name) + items.append(x) + write_item(args.output, x.variable, f.read()) + + write_entries(args.output, items) + write_footer(args.output, args.namespace) + diff --git a/rcc_format/generate.py b/rcc_format/generate.py deleted file mode 100755 index f42f6d4..0000000 --- a/rcc_format/generate.py +++ /dev/null @@ -1,9 +0,0 @@ -def generate(filelist, args): - print('', file=args.output) - #echo ' ' - #for f in $@; do - # echo ' '$f''; - #done - #echo ' ' - print('', file=args.output) - diff --git a/rcc_format/util.py b/rcc_format/util.py index 7fcfe34..af6651b 100644 --- a/rcc_format/util.py +++ b/rcc_format/util.py @@ -9,59 +9,55 @@ def to_variable_name(path): name = name[:-5] name = name.replace('-', '_') name = name.replace('.', '_') - return name - -def filelist(file): - root = xml.parse(file).getroot() - if root.tag != 'RCC': - return None - - files = [] - for child in root: - if child.tag == 'qresource': - prefix = child.attrib['prefix'] - for i in child: - alias = prefix + '/' + i.attrib['alias'] - variable = to_variable_name(i.text) - path = i.text - files.append(resource(alias, variable, path)) - - return files + return "r__" + name def write_header(file, namespace): - print("// Autogenerated binary file hexdump", file=file) - print("// This file may get overwritten by the build system\n", file=file) - print("#include \n", file=file) + print("""// Autogenerated header - this file may get overwritten by the build system +#pragma once + +#include +#include +#include +#include +""", file=file) print("namespace {} {{".format(namespace), file=file) def write_item(file, array_name, array_data): - line_items = 0 - - print("constexpr uint8_t {}[] = {{".format(array_name), file=file) + print("constexpr char {}[] = {{".format(array_name), file=file) - for byte in array_data[0:len(array_data)]: + line_items = 0 + for byte in array_data: line_items+=1 if line_items == 16: - print(" 0x{:02X},".format(byte), file=file) + print("0x{:02X},".format(byte), file=file) line_items = 0 else: - print(" 0x{:02X},".format(byte), file=file, end='') - + print("0x{:02X},".format(byte), file=file, end='') print("};", file=file) print("constexpr size_t {}_len = {};\n".format(array_name, len(array_data)), file=file) def write_entries(file, resource_list): print("constexpr std::array entries {", file=file) - for f in resource_list: - print(" \"{}\", ".format(f.alias), file=file) - print("};\n", file=file) - print("constexpr std::array values {", file=file) for f in resource_list: - print(" std::span( {}, {}_len ),".format(f.variable, f.variable), file=file) + print(" std::make_tuple(\"{}\", std::span({}, {}_len))," + .format(f.alias, f.variable, f.variable), file=file) + print("};\n", file=file) def write_footer(file, namespace): + print("""template +constexpr auto get(StrViewLambda name) { + static_assert(std::is_same_v); + + constexpr auto x = std::find_if(icons::entries.begin(), icons::entries.end(), + [=](const auto &tuple) -> bool { + return std::get<0>(tuple) == name(); + }); + static_assert(x != icons::entries.end()); + + return std::get<1>(*x); +}""", file=file) print("\n}} // namespace {}".format(namespace), file=file) diff --git a/rcc_format/zstd.py b/rcc_format/zstd.py deleted file mode 100644 index 34eeb64..0000000 --- a/rcc_format/zstd.py +++ /dev/null @@ -1,43 +0,0 @@ -import subprocess -from rcc_format.util import * - -def zstd(filelist, args): - if args.train is not None: - train(filelist, args.train, args.binary, args.dsize) - return - - write_header(args.output, args.namespace) - - for f in filelist: - with open(f.path, 'rb') as contents: - write_item(args.output, f.variable, compress(contents, args.binary, args.level, dictionary=args.dict)) - - write_entries(args.output, filelist) - if args.dict is not None: - write_item(args.output, 'dict', args.dict.read()) - print("constexpr auto dictionary = std::span(dict, dict_len);", file=args.output) - else: - print("constexpr std::span dictionary {};", file=args.output) - - print("constexpr auto compression = embed::Zstd;", file=args.output) - - write_footer(args.output, args.namespace) - -def train(filelist, output, zstd_bin, maxdict): - cmd = [ zstd_bin, '--train', '--maxdict=' + str(maxdict), '-o', output.name ] - - for f in filelist: - cmd.append(f.path) - - subprocess.run(cmd) - -def compress(file, zstd_bin, level, dictionary=None): - cmd = [ zstd_bin, '--compress', '--stdout', '-' + str(level) ] - - if dictionary is not None: - cmd.append('-D') - cmd.append(dictionary.name) - - cmd.append(file.name) - return subprocess.run(cmd, capture_output=True).stdout - diff --git a/readme.md b/readme.md index 9c745ff..c7a8bdd 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,9 @@ ## rcc -This is a resource compiler for C++, similar to Qt's rcc. It will generate hexdumps of files, optionally compress them with zstd. +This is a file dump utility that produces C++ headers. ### requirements * rcc tool requires python -* zstd compression requires zstd * c++20 (std::span) ### license diff --git a/test/main.cpp b/test/main.cpp deleted file mode 100644 index f18332c..0000000 --- a/test/main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "resources.h" -#include "zstd_resources.h" -#include -#include - -int main(int, char **) -{ - constexpr auto size = staticdata::entries.size(); - constexpr embed::Resources static_ctx(staticdata::entries, staticdata::values); - const embed::CompressedResources zstd_ctx(zstd_data::entries, zstd_data::values, zstd_data::compression, zstd_data::dictionary); - for(std::size_t i = 0; i < size; ++i) { - const auto s = static_ctx.value(i); - const auto v = zstd_ctx.decompress(i); - - if(s.size() != v.size()) { - printf(" raw data at path [%s] size=%li\n", staticdata::entries.at(i), s.size()); - printf(" zstd data at path [%s] size=%li\n", zstd_data::entries.at(i), v.size()); - return EXIT_FAILURE; - } - - if(!std::equal(s.begin(), s.end(), v.begin(), v.end())) { - printf("failed comparing values at path [%s]\n", staticdata::entries.at(i)); - for(const char &c : s) - printf("%c", c); - for(const char &c : v) - printf("%c", c); - return EXIT_FAILURE; - } - } - printf("Zstd compression test complete\n"); - - return EXIT_SUCCESS; -} diff --git a/test/resources.xrc b/test/resources.xrc deleted file mode 100644 index deb35d9..0000000 --- a/test/resources.xrc +++ /dev/null @@ -1,377 +0,0 @@ - - - ../3rd-party/tabler-icons/icons/activity.svg - ../3rd-party/tabler-icons/icons/ad.svg - ../3rd-party/tabler-icons/icons/adjustments.svg - ../3rd-party/tabler-icons/icons/alarm.svg - ../3rd-party/tabler-icons/icons/alert-circle.svg - ../3rd-party/tabler-icons/icons/alert-triangle.svg - ../3rd-party/tabler-icons/icons/align-center.svg - ../3rd-party/tabler-icons/icons/align-justified.svg - ../3rd-party/tabler-icons/icons/align-left.svg - ../3rd-party/tabler-icons/icons/align-right.svg - ../3rd-party/tabler-icons/icons/aperture.svg - ../3rd-party/tabler-icons/icons/archive.svg - ../3rd-party/tabler-icons/icons/arrow-back.svg - ../3rd-party/tabler-icons/icons/arrow-bar-down.svg - ../3rd-party/tabler-icons/icons/arrow-bar-left.svg - ../3rd-party/tabler-icons/icons/arrow-bar-right.svg - ../3rd-party/tabler-icons/icons/arrow-bar-up.svg - ../3rd-party/tabler-icons/icons/arrow-down-circle.svg - ../3rd-party/tabler-icons/icons/arrow-down-left-circle.svg - ../3rd-party/tabler-icons/icons/arrow-down-left.svg - ../3rd-party/tabler-icons/icons/arrow-down-right-circle.svg - ../3rd-party/tabler-icons/icons/arrow-down-right.svg - ../3rd-party/tabler-icons/icons/arrow-down.svg - ../3rd-party/tabler-icons/icons/arrow-forward.svg - ../3rd-party/tabler-icons/icons/arrow-left-circle.svg - ../3rd-party/tabler-icons/icons/arrow-left.svg - ../3rd-party/tabler-icons/icons/arrow-narrow-down.svg - ../3rd-party/tabler-icons/icons/arrow-narrow-left.svg - ../3rd-party/tabler-icons/icons/arrow-narrow-right.svg - ../3rd-party/tabler-icons/icons/arrow-narrow-up.svg - ../3rd-party/tabler-icons/icons/arrow-right-circle.svg - ../3rd-party/tabler-icons/icons/arrow-right.svg - ../3rd-party/tabler-icons/icons/arrow-up-circle.svg - ../3rd-party/tabler-icons/icons/arrow-up-left-circle.svg - ../3rd-party/tabler-icons/icons/arrow-up-left.svg - ../3rd-party/tabler-icons/icons/arrow-up-right-circle.svg - ../3rd-party/tabler-icons/icons/arrow-up-right.svg - ../3rd-party/tabler-icons/icons/arrow-up.svg - ../3rd-party/tabler-icons/icons/arrows-diagonal-2.svg - ../3rd-party/tabler-icons/icons/arrows-diagonal.svg - ../3rd-party/tabler-icons/icons/arrows-horizontal.svg - ../3rd-party/tabler-icons/icons/arrows-maximize.svg - ../3rd-party/tabler-icons/icons/arrows-minimize.svg - ../3rd-party/tabler-icons/icons/arrows-sort.svg - ../3rd-party/tabler-icons/icons/arrows-vertical.svg - ../3rd-party/tabler-icons/icons/artboard.svg - ../3rd-party/tabler-icons/icons/at.svg - ../3rd-party/tabler-icons/icons/award.svg - ../3rd-party/tabler-icons/icons/backspace.svg - ../3rd-party/tabler-icons/icons/ban.svg - ../3rd-party/tabler-icons/icons/battery-1.svg - ../3rd-party/tabler-icons/icons/battery-2.svg - ../3rd-party/tabler-icons/icons/battery-3.svg - ../3rd-party/tabler-icons/icons/battery-4.svg - ../3rd-party/tabler-icons/icons/battery-charging.svg - ../3rd-party/tabler-icons/icons/battery.svg - ../3rd-party/tabler-icons/icons/bed.svg - ../3rd-party/tabler-icons/icons/bell.svg - ../3rd-party/tabler-icons/icons/bike.svg - ../3rd-party/tabler-icons/icons/bluetooth.svg - ../3rd-party/tabler-icons/icons/bolt.svg - ../3rd-party/tabler-icons/icons/book.svg - ../3rd-party/tabler-icons/icons/bookmark.svg - ../3rd-party/tabler-icons/icons/border-all.svg - ../3rd-party/tabler-icons/icons/border-bottom.svg - ../3rd-party/tabler-icons/icons/border-horizontal.svg - ../3rd-party/tabler-icons/icons/border-inner.svg - ../3rd-party/tabler-icons/icons/border-left.svg - ../3rd-party/tabler-icons/icons/border-none.svg - ../3rd-party/tabler-icons/icons/border-outer.svg - ../3rd-party/tabler-icons/icons/border-right.svg - ../3rd-party/tabler-icons/icons/border-top.svg - ../3rd-party/tabler-icons/icons/border-vertical.svg - ../3rd-party/tabler-icons/icons/box.svg - ../3rd-party/tabler-icons/icons/briefcase.svg - ../3rd-party/tabler-icons/icons/bucket.svg - ../3rd-party/tabler-icons/icons/bug.svg - ../3rd-party/tabler-icons/icons/building-arch.svg - ../3rd-party/tabler-icons/icons/building-bridge-2.svg - ../3rd-party/tabler-icons/icons/building-bridge.svg - ../3rd-party/tabler-icons/icons/building-church.svg - ../3rd-party/tabler-icons/icons/building-hospital.svg - ../3rd-party/tabler-icons/icons/building-store.svg - ../3rd-party/tabler-icons/icons/building.svg - ../3rd-party/tabler-icons/icons/bulb-off.svg - ../3rd-party/tabler-icons/icons/bulb.svg - ../3rd-party/tabler-icons/icons/calendar-event.svg - ../3rd-party/tabler-icons/icons/calendar.svg - ../3rd-party/tabler-icons/icons/camera.svg - ../3rd-party/tabler-icons/icons/caret-down.svg - ../3rd-party/tabler-icons/icons/caret-left.svg - ../3rd-party/tabler-icons/icons/caret-right.svg - ../3rd-party/tabler-icons/icons/caret-up.svg - ../3rd-party/tabler-icons/icons/cash.svg - ../3rd-party/tabler-icons/icons/cast.svg - ../3rd-party/tabler-icons/icons/chart-area-line.svg - ../3rd-party/tabler-icons/icons/chart-area.svg - ../3rd-party/tabler-icons/icons/chart-bar.svg - ../3rd-party/tabler-icons/icons/chart-candle.svg - ../3rd-party/tabler-icons/icons/chart-donut.svg - ../3rd-party/tabler-icons/icons/chart-line.svg - ../3rd-party/tabler-icons/icons/chart-pie.svg - ../3rd-party/tabler-icons/icons/check.svg - ../3rd-party/tabler-icons/icons/chevron-down.svg - ../3rd-party/tabler-icons/icons/chevron-left.svg - ../3rd-party/tabler-icons/icons/chevron-right.svg - ../3rd-party/tabler-icons/icons/chevron-up.svg - ../3rd-party/tabler-icons/icons/chevrons-down.svg - ../3rd-party/tabler-icons/icons/chevrons-left.svg - ../3rd-party/tabler-icons/icons/chevrons-right.svg - ../3rd-party/tabler-icons/icons/chevrons-up.svg - ../3rd-party/tabler-icons/icons/circle-check.svg - ../3rd-party/tabler-icons/icons/circle-minus.svg - ../3rd-party/tabler-icons/icons/circle-plus.svg - ../3rd-party/tabler-icons/icons/circle-x.svg - ../3rd-party/tabler-icons/icons/circle.svg - ../3rd-party/tabler-icons/icons/clipboard-check.svg - ../3rd-party/tabler-icons/icons/clipboard-list.svg - ../3rd-party/tabler-icons/icons/clipboard-x.svg - ../3rd-party/tabler-icons/icons/clipboard.svg - ../3rd-party/tabler-icons/icons/clock.svg - ../3rd-party/tabler-icons/icons/cloud-download.svg - ../3rd-party/tabler-icons/icons/cloud-rain.svg - ../3rd-party/tabler-icons/icons/cloud-snow.svg - ../3rd-party/tabler-icons/icons/cloud-storm.svg - ../3rd-party/tabler-icons/icons/cloud-upload.svg - ../3rd-party/tabler-icons/icons/cloud.svg - ../3rd-party/tabler-icons/icons/code.svg - ../3rd-party/tabler-icons/icons/color-swatch.svg - ../3rd-party/tabler-icons/icons/command.svg - ../3rd-party/tabler-icons/icons/compass.svg - ../3rd-party/tabler-icons/icons/copy.svg - ../3rd-party/tabler-icons/icons/copyright.svg - ../3rd-party/tabler-icons/icons/corner-down-left.svg - ../3rd-party/tabler-icons/icons/corner-down-right.svg - ../3rd-party/tabler-icons/icons/corner-left-down.svg - ../3rd-party/tabler-icons/icons/corner-left-up.svg - ../3rd-party/tabler-icons/icons/corner-right-down.svg - ../3rd-party/tabler-icons/icons/corner-right-up.svg - ../3rd-party/tabler-icons/icons/corner-up-left.svg - ../3rd-party/tabler-icons/icons/corner-up-right.svg - ../3rd-party/tabler-icons/icons/credit-card.svg - ../3rd-party/tabler-icons/icons/crop.svg - ../3rd-party/tabler-icons/icons/cut.svg - ../3rd-party/tabler-icons/icons/dashboard.svg - ../3rd-party/tabler-icons/icons/database.svg - ../3rd-party/tabler-icons/icons/device-desktop.svg - ../3rd-party/tabler-icons/icons/device-floppy.svg - ../3rd-party/tabler-icons/icons/device-gamepad.svg - ../3rd-party/tabler-icons/icons/device-laptop.svg - ../3rd-party/tabler-icons/icons/device-mobile.svg - ../3rd-party/tabler-icons/icons/device-speaker.svg - ../3rd-party/tabler-icons/icons/device-tablet.svg - ../3rd-party/tabler-icons/icons/device-tv.svg - ../3rd-party/tabler-icons/icons/diamond.svg - ../3rd-party/tabler-icons/icons/dice.svg - ../3rd-party/tabler-icons/icons/directions.svg - ../3rd-party/tabler-icons/icons/disabled.svg - ../3rd-party/tabler-icons/icons/disc.svg - ../3rd-party/tabler-icons/icons/dots-circle-horizontal.svg - ../3rd-party/tabler-icons/icons/dots-diagonal-2.svg - ../3rd-party/tabler-icons/icons/dots-diagonal.svg - ../3rd-party/tabler-icons/icons/dots-vertical.svg - ../3rd-party/tabler-icons/icons/dots.svg - ../3rd-party/tabler-icons/icons/download.svg - ../3rd-party/tabler-icons/icons/droplet.svg - ../3rd-party/tabler-icons/icons/edit.svg - ../3rd-party/tabler-icons/icons/external-link.svg - ../3rd-party/tabler-icons/icons/eye.svg - ../3rd-party/tabler-icons/icons/face-id.svg - ../3rd-party/tabler-icons/icons/file-check.svg - ../3rd-party/tabler-icons/icons/file-download.svg - ../3rd-party/tabler-icons/icons/file-invoice.svg - ../3rd-party/tabler-icons/icons/file-minus.svg - ../3rd-party/tabler-icons/icons/file-music.svg - ../3rd-party/tabler-icons/icons/file-plus.svg - ../3rd-party/tabler-icons/icons/file-shredder.svg - ../3rd-party/tabler-icons/icons/file-text.svg - ../3rd-party/tabler-icons/icons/file-x.svg - ../3rd-party/tabler-icons/icons/file.svg - ../3rd-party/tabler-icons/icons/filter.svg - ../3rd-party/tabler-icons/icons/flag.svg - ../3rd-party/tabler-icons/icons/flip-horizontal.svg - ../3rd-party/tabler-icons/icons/flip-vertical.svg - ../3rd-party/tabler-icons/icons/floppy-disk.svg - ../3rd-party/tabler-icons/icons/folder-minus.svg - ../3rd-party/tabler-icons/icons/folder-plus.svg - ../3rd-party/tabler-icons/icons/folder-x.svg - ../3rd-party/tabler-icons/icons/folder.svg - ../3rd-party/tabler-icons/icons/folders.svg - ../3rd-party/tabler-icons/icons/frame.svg - ../3rd-party/tabler-icons/icons/friends.svg - ../3rd-party/tabler-icons/icons/gauge.svg - ../3rd-party/tabler-icons/icons/gift.svg - ../3rd-party/tabler-icons/icons/git-branch.svg - ../3rd-party/tabler-icons/icons/git-commit.svg - ../3rd-party/tabler-icons/icons/git-compare.svg - ../3rd-party/tabler-icons/icons/git-merge.svg - ../3rd-party/tabler-icons/icons/git-pull-request.svg - ../3rd-party/tabler-icons/icons/glass-full.svg - ../3rd-party/tabler-icons/icons/glass.svg - ../3rd-party/tabler-icons/icons/globe.svg - ../3rd-party/tabler-icons/icons/grid-dots.svg - ../3rd-party/tabler-icons/icons/grid.svg - ../3rd-party/tabler-icons/icons/hash.svg - ../3rd-party/tabler-icons/icons/headphones.svg - ../3rd-party/tabler-icons/icons/heart.svg - ../3rd-party/tabler-icons/icons/help.svg - ../3rd-party/tabler-icons/icons/home-2.svg - ../3rd-party/tabler-icons/icons/home.svg - ../3rd-party/tabler-icons/icons/ice-cream.svg - ../3rd-party/tabler-icons/icons/id.svg - ../3rd-party/tabler-icons/icons/inbox.svg - ../3rd-party/tabler-icons/icons/infinity.svg - ../3rd-party/tabler-icons/icons/info-circle.svg - ../3rd-party/tabler-icons/icons/info-square.svg - ../3rd-party/tabler-icons/icons/key.svg - ../3rd-party/tabler-icons/icons/layers-difference.svg - ../3rd-party/tabler-icons/icons/layers-intersect.svg - ../3rd-party/tabler-icons/icons/layers-subtract.svg - ../3rd-party/tabler-icons/icons/layers-union.svg - ../3rd-party/tabler-icons/icons/layout-2.svg - ../3rd-party/tabler-icons/icons/layout-align-bottom.svg - ../3rd-party/tabler-icons/icons/layout-align-center.svg - ../3rd-party/tabler-icons/icons/layout-align-left.svg - ../3rd-party/tabler-icons/icons/layout-align-middle.svg - ../3rd-party/tabler-icons/icons/layout-align-right.svg - ../3rd-party/tabler-icons/icons/layout-align-top.svg - ../3rd-party/tabler-icons/icons/layout-bottombar.svg - ../3rd-party/tabler-icons/icons/layout-columns.svg - ../3rd-party/tabler-icons/icons/layout-distribute-horizontal.svg - ../3rd-party/tabler-icons/icons/layout-distribute-vertical.svg - ../3rd-party/tabler-icons/icons/layout-navbar.svg - ../3rd-party/tabler-icons/icons/layout-rows.svg - ../3rd-party/tabler-icons/icons/layout-sidebar-right.svg - ../3rd-party/tabler-icons/icons/layout-sidebar.svg - ../3rd-party/tabler-icons/icons/layout.svg - ../3rd-party/tabler-icons/icons/lego.svg - ../3rd-party/tabler-icons/icons/lifebuoy.svg - ../3rd-party/tabler-icons/icons/link.svg - ../3rd-party/tabler-icons/icons/list-check.svg - ../3rd-party/tabler-icons/icons/list.svg - ../3rd-party/tabler-icons/icons/live-photo.svg - ../3rd-party/tabler-icons/icons/location.svg - ../3rd-party/tabler-icons/icons/lock-open.svg - ../3rd-party/tabler-icons/icons/lock.svg - ../3rd-party/tabler-icons/icons/magnet.svg - ../3rd-party/tabler-icons/icons/mail-opened.svg - ../3rd-party/tabler-icons/icons/mail.svg - ../3rd-party/tabler-icons/icons/man.svg - ../3rd-party/tabler-icons/icons/map-2.svg - ../3rd-party/tabler-icons/icons/map-pin.svg - ../3rd-party/tabler-icons/icons/map.svg - ../3rd-party/tabler-icons/icons/maximize.svg - ../3rd-party/tabler-icons/icons/menu.svg - ../3rd-party/tabler-icons/icons/message-2.svg - ../3rd-party/tabler-icons/icons/message-circle.svg - ../3rd-party/tabler-icons/icons/message-dots.svg - ../3rd-party/tabler-icons/icons/message.svg - ../3rd-party/tabler-icons/icons/messages.svg - ../3rd-party/tabler-icons/icons/microphone.svg - ../3rd-party/tabler-icons/icons/minimize.svg - ../3rd-party/tabler-icons/icons/minus.svg - ../3rd-party/tabler-icons/icons/mood-confuzed.svg - ../3rd-party/tabler-icons/icons/mood-happy.svg - ../3rd-party/tabler-icons/icons/mood-neutral.svg - ../3rd-party/tabler-icons/icons/mood-sad.svg - ../3rd-party/tabler-icons/icons/mood-smile.svg - ../3rd-party/tabler-icons/icons/moon.svg - ../3rd-party/tabler-icons/icons/mouse.svg - ../3rd-party/tabler-icons/icons/movie.svg - ../3rd-party/tabler-icons/icons/mug.svg - ../3rd-party/tabler-icons/icons/music.svg - ../3rd-party/tabler-icons/icons/news.svg - ../3rd-party/tabler-icons/icons/note.svg - ../3rd-party/tabler-icons/icons/notes.svg - ../3rd-party/tabler-icons/icons/notification.svg - ../3rd-party/tabler-icons/icons/package.svg - ../3rd-party/tabler-icons/icons/paint.svg - ../3rd-party/tabler-icons/icons/palette.svg - ../3rd-party/tabler-icons/icons/paperclip.svg - ../3rd-party/tabler-icons/icons/parking.svg - ../3rd-party/tabler-icons/icons/pencil.svg - ../3rd-party/tabler-icons/icons/phone-call.svg - ../3rd-party/tabler-icons/icons/phone-incoming.svg - ../3rd-party/tabler-icons/icons/phone-outgoing.svg - ../3rd-party/tabler-icons/icons/phone-pause.svg - ../3rd-party/tabler-icons/icons/phone.svg - ../3rd-party/tabler-icons/icons/photo.svg - ../3rd-party/tabler-icons/icons/plane.svg - ../3rd-party/tabler-icons/icons/plus.svg - ../3rd-party/tabler-icons/icons/point.svg - ../3rd-party/tabler-icons/icons/power.svg - ../3rd-party/tabler-icons/icons/presentation.svg - ../3rd-party/tabler-icons/icons/printer.svg - ../3rd-party/tabler-icons/icons/prompt.svg - ../3rd-party/tabler-icons/icons/puzzle.svg - ../3rd-party/tabler-icons/icons/qrcode.svg - ../3rd-party/tabler-icons/icons/record-mail.svg - ../3rd-party/tabler-icons/icons/refresh.svg - ../3rd-party/tabler-icons/icons/registered.svg - ../3rd-party/tabler-icons/icons/repeat-once.svg - ../3rd-party/tabler-icons/icons/repeat.svg - ../3rd-party/tabler-icons/icons/rotate-clockwise.svg - ../3rd-party/tabler-icons/icons/rotate.svg - ../3rd-party/tabler-icons/icons/route.svg - ../3rd-party/tabler-icons/icons/router.svg - ../3rd-party/tabler-icons/icons/rss.svg - ../3rd-party/tabler-icons/icons/ruler.svg - ../3rd-party/tabler-icons/icons/scissors.svg - ../3rd-party/tabler-icons/icons/search.svg - ../3rd-party/tabler-icons/icons/selector.svg - ../3rd-party/tabler-icons/icons/send.svg - ../3rd-party/tabler-icons/icons/server.svg - ../3rd-party/tabler-icons/icons/settings.svg - ../3rd-party/tabler-icons/icons/share.svg - ../3rd-party/tabler-icons/icons/shield-check.svg - ../3rd-party/tabler-icons/icons/shield-x.svg - ../3rd-party/tabler-icons/icons/shield.svg - ../3rd-party/tabler-icons/icons/shopping-cart.svg - ../3rd-party/tabler-icons/icons/sort-ascending.svg - ../3rd-party/tabler-icons/icons/sort-descending.svg - ../3rd-party/tabler-icons/icons/square-check.svg - ../3rd-party/tabler-icons/icons/square-minus.svg - ../3rd-party/tabler-icons/icons/square-plus.svg - ../3rd-party/tabler-icons/icons/square-x.svg - ../3rd-party/tabler-icons/icons/square.svg - ../3rd-party/tabler-icons/icons/stack.svg - ../3rd-party/tabler-icons/icons/star.svg - ../3rd-party/tabler-icons/icons/sticker.svg - ../3rd-party/tabler-icons/icons/sum.svg - ../3rd-party/tabler-icons/icons/sun.svg - ../3rd-party/tabler-icons/icons/switch-horizontal.svg - ../3rd-party/tabler-icons/icons/switch-vertical.svg - ../3rd-party/tabler-icons/icons/switch.svg - ../3rd-party/tabler-icons/icons/tag.svg - ../3rd-party/tabler-icons/icons/target.svg - ../3rd-party/tabler-icons/icons/temperature-celsius.svg - ../3rd-party/tabler-icons/icons/temperature-fahrenheit.svg - ../3rd-party/tabler-icons/icons/temperature.svg - ../3rd-party/tabler-icons/icons/template.svg - ../3rd-party/tabler-icons/icons/test-pipe.svg - ../3rd-party/tabler-icons/icons/thumb-down.svg - ../3rd-party/tabler-icons/icons/thumb-up.svg - ../3rd-party/tabler-icons/icons/ticket.svg - ../3rd-party/tabler-icons/icons/toggle-left.svg - ../3rd-party/tabler-icons/icons/toggle-right.svg - ../3rd-party/tabler-icons/icons/tool.svg - ../3rd-party/tabler-icons/icons/trash.svg - ../3rd-party/tabler-icons/icons/trending-down.svg - ../3rd-party/tabler-icons/icons/trending-up.svg - ../3rd-party/tabler-icons/icons/triangle.svg - ../3rd-party/tabler-icons/icons/trophy.svg - ../3rd-party/tabler-icons/icons/unlink.svg - ../3rd-party/tabler-icons/icons/upload.svg - ../3rd-party/tabler-icons/icons/urgent.svg - ../3rd-party/tabler-icons/icons/user-check.svg - ../3rd-party/tabler-icons/icons/user-minus.svg - ../3rd-party/tabler-icons/icons/user-plus.svg - ../3rd-party/tabler-icons/icons/user-x.svg - ../3rd-party/tabler-icons/icons/user.svg - ../3rd-party/tabler-icons/icons/viewfinder.svg - ../3rd-party/tabler-icons/icons/virus.svg - ../3rd-party/tabler-icons/icons/volume-2.svg - ../3rd-party/tabler-icons/icons/volume-3.svg - ../3rd-party/tabler-icons/icons/volume.svg - ../3rd-party/tabler-icons/icons/wallet.svg - ../3rd-party/tabler-icons/icons/wifi.svg - ../3rd-party/tabler-icons/icons/woman.svg - ../3rd-party/tabler-icons/icons/world.svg - ../3rd-party/tabler-icons/icons/x.svg - ../3rd-party/tabler-icons/icons/zoom-in.svg - ../3rd-party/tabler-icons/icons/zoom-out.svg - - -- cgit v1.2.1