From 746bcd25e22e492cd45a92bc9addb04cf81d208b Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Thu, 26 Mar 2020 11:28:58 +0200 Subject: Initial commit --- .gitmodules | 6 + 3rd-party/frozen | 1 + 3rd-party/tabler-icons | 1 + lib/embed.cpp | 70 +++++++++ lib/embed.h | 34 +++++ meson.build | 37 +++++ readme.md | 37 +++++ scripts/gen-resources.sh | 9 ++ scripts/rcc | 101 +++++++++++++ test/main.cpp | 17 +++ test/resources.xrc | 377 +++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 690 insertions(+) create mode 100644 .gitmodules create mode 160000 3rd-party/frozen create mode 160000 3rd-party/tabler-icons create mode 100644 lib/embed.cpp create mode 100644 lib/embed.h create mode 100644 meson.build create mode 100644 readme.md create mode 100755 scripts/gen-resources.sh create mode 100755 scripts/rcc create mode 100644 test/main.cpp create mode 100644 test/resources.xrc diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..36e8968 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "3rd-party/frozen"] + path = 3rd-party/frozen + url = https://github.com/serge-sans-paille/frozen +[submodule "3rd-party/tabler-icons"] + path = 3rd-party/tabler-icons + url = https://github.com/tabler/tabler-icons diff --git a/3rd-party/frozen b/3rd-party/frozen new file mode 160000 index 0000000..8357f86 --- /dev/null +++ b/3rd-party/frozen @@ -0,0 +1 @@ +Subproject commit 8357f86de5383dde8539734d3a1620ecc70e4a16 diff --git a/3rd-party/tabler-icons b/3rd-party/tabler-icons new file mode 160000 index 0000000..ec114c0 --- /dev/null +++ b/3rd-party/tabler-icons @@ -0,0 +1 @@ +Subproject commit ec114c0eb5bbfec35ce32ec7201be01cb01b24d5 diff --git a/lib/embed.cpp b/lib/embed.cpp new file mode 100644 index 0000000..c3b0286 --- /dev/null +++ b/lib/embed.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#include "embed.h" +#include // presumes zstd library is installed +#include + +using namespace embed; + +ZSTD_DDict* dictPtr = nullptr; + +Resources::Resources(const ResourceData &info) + : m_info(info) +{ + if(!info.dictionary.empty()) { + dictPtr = ZSTD_createDDict(info.dictionary.data(), info.dictionary.size()); + } +} + +Resources::~Resources() +{ + if(dictPtr != nullptr) { + ZSTD_freeDDict(dictPtr); + } +} + +[[nodiscard]] std::span Resources::decompress(const std::span &entry) +{ + /* 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); + auto* rBuff = new unsigned char[(size_t) 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, 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 std::span(rBuff, rSize); +} + diff --git a/lib/embed.h b/lib/embed.h new file mode 100644 index 0000000..566b14c --- /dev/null +++ b/lib/embed.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace embed { + +enum Compression { + None, + Zstd +}; + +struct ResourceData { + Compression compression = None; + std::span dictionary; +}; + +class Resources { +public: + explicit Resources(const ResourceData &info); + ~Resources(); + + [[nodiscard]] + std::span decompress(const std::span &entry); + +private: + const ResourceData m_info; +}; // class + +} + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..ffae406 --- /dev/null +++ b/meson.build @@ -0,0 +1,37 @@ +project('libembed', ['cpp'], + default_options: ['cpp_std=c++2a', 'warning_level=3'], +) + +# libstdc++ lacks std::span at the moment +add_project_arguments(['-stdlib=libc++'], language: 'cpp') +add_project_link_arguments(['-stdlib=libc++'], language : 'cpp') + +dep_zstd = dependency('libzstd') +dep_gtest = dependency('gtest') + +libembed = library('embed', + 'lib/embed.cpp', + dependencies: [ dep_zstd ], + include_directories: '3rd-party/frozen/include/' +) + +dep_libembed = declare_dependency( + link_with: libembed, + include_directories: include_directories('lib/', '3rd-party/frozen/include') +) + +prog_python = import('python').find_installation('python3') +resources_h = custom_target('resources.h', + output: 'resources.h', + input: 'scripts/rcc', + command: [prog_python, '@INPUT@', '--compress=Zstd', '--dict=dictionary', '--output=@OUTPUT@', meson.current_source_dir()/'3rd-party/tabler-icons/icons/chevron-up.svg'], +) + +test('libembed', + executable('embed', + sources: [ 'test/main.cpp', resources_h ], + dependencies: [ dep_gtest, dep_libembed ] + ), + env: environment({ 'CONFIGFILE' : meson.current_source_dir()/'test/defaultrc.ini' }), + workdir: meson.current_source_dir()/'test' +) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d75ac34 --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +## rcc + +This is a resource compiler for C++, similar to Qt's rcc. It will generate hexdumps of files, optionally compress them with zstd. + +### requirements +* rcc tool requires python +* zstd compression requires zstd + +libembed: +* c++20 +* frozen: https://github.com/serge-sans-paille/frozen (constexpr string and unordered_map) + +### license + +Copyright (c) 2020 aqua@iserlohn-fortress.net + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + diff --git a/scripts/gen-resources.sh b/scripts/gen-resources.sh new file mode 100755 index 0000000..a1db9fd --- /dev/null +++ b/scripts/gen-resources.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo '' +echo ' ' +for f in $@; do + echo ' '$f''; +done +echo ' ' +echo '' diff --git a/scripts/rcc b/scripts/rcc new file mode 100755 index 0000000..2ca587c --- /dev/null +++ b/scripts/rcc @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +import argparse +import sys +import os.path +import subprocess +import xml.etree.ElementTree as xml + +def train(files, output, zstd='zstd', maxdict=512): + cmd = [ zstd, '--train', '--stdout', '--maxdict=' + str(maxdict), '-o', output ] + + for f in files: + cmd.append(f.name) + + subprocess.run(cmd) + +def compress(file, zstd='zstd', level=19, dictionary=None): + cmd = [ zstd, '--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 + +def hexdump(array_name, input_file, out_h): + array_len = 0 + + print("constexpr unsigned char {}[] = {{".format(array_name), file=out_h) + + for byte in input_file[0:len(input_file)]: + array_len+=1 + if array_len%16 == 0: + print(" 0x{:02X},".format(byte), file=out_h) + else: + print(" 0x{:02X},".format(byte), file=out_h, end='') + + + print("};", file=out_h) + print("constexpr size_t {}_len = {};\n".format(array_name, array_len), file=out_h) + +def name(path): + name = path.replace('/', '_') + if name.endswith('.zstd'): + name = name[:-5] + name = name.replace('-', '_') + name = name.replace('.', '_') + return name + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description='Resource Compiler for C++', + epilog='If using compression, make sure the required dependencies are provided.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + parser.add_argument('input', type=argparse.FileType('rt'), help='input file (.xrc)') + parser.add_argument('-o', '--output', type=argparse.FileType('wt'), metavar='OUT', default=sys.stdout, help='output header file') + + parser.add_argument('--compress', choices=[ 'None', 'Zstd' ], help='compress input files using algorightm') + parser.add_argument('--dict', type=argparse.FileType('rb'), help='[zstd] use specified dictionary, recommended for many similar small files') + parser.add_argument('--train', action='store_true', help='[zstd] train dictionary') + + args=parser.parse_args() + + entries_list = "" + + if args.compress=='Zstd' and args.train: + train(args.input, args.dict.name) + + # write header + print("// Autogenerated binary file hexdump", file=args.output) + print("// This file may get overwritten by the build system\n", file=args.output) + print("#include \n", file=args.output) + + # write file data + for child in xml.parse(args.input).getroot(): + if child.tag == 'qresource': + prefix = child.attrib['prefix'] + for i in child: + vname = name(i.text) + with open(i.text) as f: + hexdump(vname, compress(f, dictionary=args.dict), args.output) + entries_list += " {{ \"{}/{}\", std::span({}, {}_len) }},\n".format(prefix, i.attrib['alias'], vname, vname) + + # write dictionary + if args.dict is not None: + hexdump('dictionary', args.dict.read(), args.output) + + # write entries + print("constexpr auto entries = frozen::make_unordered_map>({", file=args.output) + print(entries_list, file=args.output) + print("});\n", file=args.output) + + # write metadata struct + print("constexpr embed::ResourceData metadata = {", file=args.output) + print(" .compression = embed::{},".format(args.compress), file=args.output) + if args.dict is not None: + print(" .dictionary = std::span(dictionary, dictionary_len),", file=args.output) + print("};", file=args.output) diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..faf5a16 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,17 @@ +#include +#include "resources.h" +#include + +int main(int argc, char** argv) +{ +// testing::InitGoogleTest(&argc, argv); +// return RUN_ALL_TESTS(); + embed::Resources ctx(metadata); + constexpr auto raw = entries.at("/icons/chevron-up.svg"); + static_assert(!raw.empty()); + const auto x = ctx.decompress(raw); + for(const char c : x) + printf("%c", c); + delete[] x.data(); + return 0; +} diff --git a/test/resources.xrc b/test/resources.xrc new file mode 100644 index 0000000..deb35d9 --- /dev/null +++ b/test/resources.xrc @@ -0,0 +1,377 @@ + + + ../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