Alternative implementation for resources for the non-Qt case.

This commit is contained in:
Matthias Koefferlein 2022-05-28 00:33:08 +02:00
parent b22c7091ae
commit ca3d840cb2
10 changed files with 524 additions and 23 deletions

154
scripts/pyqrc.py Executable file
View File

@ -0,0 +1,154 @@
# A lean substitute for QRC which is employed in the non-Qt case
import xml.etree.ElementTree as et
import argparse
import zlib
import os
# A class providing the generator
class RCFile(object):
def __init__(self, alias, path):
self.path = path
self.alias = alias
def write(self, file, index):
f = open(self.path, "rb")
raw_data = f.read()
f.close()
data = zlib.compress(raw_data)
compressed = "true"
cb = "{"
bc = "}"
cls = "Resource" + str(index)
file.write(f"\n// Resource file {self.path} as {self.alias}\n")
file.write( "namespace {\n")
file.write( "\n")
file.write(f" class {cls}\n")
file.write( " {\n")
file.write(f" {cls}() {cb}\n")
file.write(f" static bool compressed = {compressed};\n")
file.write(f" static const char *name = \"{self.alias}\";\n")
file.write( " static const unsigned char data[] = {")
n = 0
for b in data:
if n == 0:
file.write("\n ")
file.write("0x%02x," % b)
n += 1
if n == 16:
n = 0
file.write( "\n")
file.write( " };\n")
file.write( " m_id = tl::register_resource(name, compressed, data, sizeof(data) / sizeof(data[0]));\n")
file.write( " }\n")
file.write(f" ~{cls}() {cb}\n")
file.write( " tl::unregister_resource(m_id)\n")
file.write( " }\n")
file.write( " tl::resource_id_type m_id;\n")
file.write(f" {bc} resource_instance{index}\n")
file.write( "\n")
file.write( "}\n")
class RCGenerator(object):
def __init__(self):
self.files = []
def append(self, path, alias):
self.files.append(RCFile(path, alias))
def dump_files(self):
for f in self.files:
print(f.path)
def write(self, file):
file.write(f"""
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* DO NOT EDIT THIS FILE.
* This file has been created automatically
*/
#include "tlResources.h"
""")
i = 1
for f in self.files:
f.write(file, i)
i += 1
# The main code
generator = RCGenerator()
# argument parsing
parser = argparse.ArgumentParser(description='Lean QRC parser')
parser.add_argument('input', type=str, nargs='+',
help='The QRC input file')
parser.add_argument('--output', '-o', type=str, nargs='?',
help='The C++ output file')
parser.add_argument('--path', '-p', type=str, nargs='?',
help='Path to the input files (default is current directory)')
args = parser.parse_args()
# read the input file
for input in args.input:
root_node = et.parse(input).getroot()
for qresource in root_node.findall('qresource'):
prefix = qresource.get('prefix')
for file in qresource.findall('file'):
alias = file.get('alias')
path = file.text
if alias is None:
alias = path
if prefix is not None:
alias = prefix + "/" + alias
if args.path is not None:
path = os.path.join(args.path, path)
else:
path = os.path.join(os.path.dirname(input), path)
generator.append(alias, path)
# produce the output file
if args.output is not None:
f = open(args.output, "w")
generator.write(f)
f.close()
else:
generator.dump_files()

View File

@ -13,7 +13,7 @@ HEADERS = \
drcCommon.h \
drcForceLink.h \
!equals(HAVE_QT, "0") {
!equals(HAVE_QT, "0") || !equals(HAVE_PYTHON, "0") {
RESOURCES = \
drcResources.qrc
}

View File

@ -182,6 +182,17 @@ equals(HAVE_QT, "0") {
QT =
# fake qrc made with python
!equals(HAVE_PYTHON, "0") {
new_qrc.output = qrc_${QMAKE_FILE_BASE}.cc
new_qrc.commands = $$PYTHON $$PWD/../scripts/pyqrc.py ${QMAKE_FILE_NAME} -o ${QMAKE_FILE_OUT}
new_qrc.depend_command = $$PYTHON $$PWD/../scripts/pyqrc.py ${QMAKE_FILE_NAME}
new_qrc.input = RESOURCES
new_qrc.variable_out = SOURCES
new_qrc.CONFIG += dep_lines
QMAKE_EXTRA_COMPILERS += new_qrc
}
} else {
DEFINES += HAVE_QT
@ -290,3 +301,4 @@ DEFINES += \
KLAYOUT_TINY_VERSION=$$KLAYOUT_TINY_VERSION \
VERSION = $$KLAYOUT_VERSION

View File

@ -26,6 +26,7 @@ SOURCES = \
tlLog.cc \
tlObject.cc \
tlProgress.cc \
tlResources.cc \
tlScriptError.cc \
tlSleep.cc \
tlStaticObjects.cc \
@ -76,6 +77,7 @@ HEADERS = \
tlObject.h \
tlObjectCollection.h \
tlProgress.h \
tlResources.h \
tlReuseVector.h \
tlScriptError.h \
tlSleep.h \

127
src/tl/tl/tlResources.cc Normal file
View File

@ -0,0 +1,127 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlResources.h"
#include <map>
#include <vector>
namespace tl
{
namespace {
class ResourceDict
{
public:
struct DictEntry
{
const unsigned char *data;
size_t data_size;
bool compressed;
};
ResourceDict () { }
resource_id_type add (const char *name, bool compressed, const unsigned char *data, size_t data_size)
{
m_dict[std::string (name)] = m_entries.size ();
m_entries.push_back (DictEntry ());
m_entries.back ().data = data;
m_entries.back ().data_size = data_size;
m_entries.back ().compressed = compressed;
return m_entries.size () - 1;
}
void remove (resource_id_type id)
{
if (id < m_entries.size ()) {
m_entries [id].data = 0;
m_entries [id].data_size = 0;
}
}
DictEntry *entry (const char *name)
{
auto i = m_dict.find (std::string (name));
if (i != m_dict.end () && i->second < m_entries.size ()) {
return &m_entries [i->second];
} else {
return 0;
}
}
private:
std::map<std::string, resource_id_type> m_dict;
std::vector<DictEntry> m_entries;
};
}
static ResourceDict *ms_dict = 0;
resource_id_type register_resource (const char *name, bool compressed, const unsigned char *data, size_t data_size)
{
if (! ms_dict) {
ms_dict = new ResourceDict ();
}
return ms_dict->add (name, compressed, data, data_size);
}
void unregister_resource (size_t id)
{
if (ms_dict) {
ms_dict->remove (id);
}
}
tl::InputStream *get_resource (const char *name)
{
if (! ms_dict) {
return 0;
}
ResourceDict::DictEntry *entry = ms_dict->entry (name);
if (! entry || ! entry->data) {
return 0;
}
if (entry->compressed) {
tl_assert (entry->data_size > 6);
// NOTE: zlib compression (used in pyqrc) adds two bytes header before the data block and
// 4 bytes after (CRC32)
auto stream = new tl::InputStream (new tl::InputMemoryStream ((const char *) entry->data + 2, entry->data_size - 6));
stream->inflate ();
return stream;
} else {
// raw data
return new tl::InputStream (new tl::InputMemoryStream ((const char *) entry->data, entry->data_size));
}
}
}

74
src/tl/tl/tlResources.h Normal file
View File

@ -0,0 +1,74 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlResources
#define HDR_tlResources
#include "tlAssert.h"
#include "tlStream.h"
#include <cstddef>
namespace tl
{
typedef size_t resource_id_type;
/**
* @brief A facility for retrieving resource data similar to Qt resources
*
* This feature is intended to substitute Qt resources when Qt is not available.
*/
/**
* @brief Registers the resource data under the given name
*
* @param name The name of the resource
* @param compressed True, if the data is gzip-compressed
* @param data The data pointer - needs to stay valid during the lifetime of the application
* @param data_size The size of the data block
*
* The return value is an Id by which the resource can be unregistered.
*/
TL_PUBLIC resource_id_type register_resource (const char *name, bool compressed, const unsigned char *data, size_t data_size);
/**
* @brief Registers the resource data under the given name
*
* @param id The id of the resource to unregister (see "register_resource")
*/
TL_PUBLIC void unregister_resource (size_t id);
/**
* @brief Gets the resource data as a stream
*
* @param name The resource name
* @return A tl::InputStream object delivering the data or 0 if there is no such resource
* It is the responsibility of the called to delete the input stream.
*/
TL_PUBLIC tl::InputStream *get_resource (const char *name);
}
#endif

View File

@ -285,7 +285,7 @@ InputStream::get (size_t n, bool bypass_inflate)
delete mp_inflate;
mp_inflate = 0;
}
}
}
if (m_blen < n) {
@ -341,16 +341,36 @@ std::string
InputStream::read_all (size_t max_count)
{
std::string str;
while (max_count > 0) {
size_t n = std::min (max_count, std::max (size_t (1), m_blen));
const char *b = get (n);
if (b) {
str += std::string (b, n);
max_count -= n;
} else {
break;
if (mp_inflate) {
// Inflate is special - it does not have a guaranteed byte delivery, so we have to go the
// hard way and pick the file byte by byte
while (max_count > 0) {
const char *b = get (1);
if (b) {
str += *b;
--max_count;
} else {
break;
}
}
}
} else {
while (max_count > 0) {
size_t n = std::min (max_count, std::max (size_t (1), m_blen));
const char *b = get (n);
if (b) {
str += std::string (b, n);
max_count -= n;
} else {
break;
}
}
}
return str;
}
@ -358,15 +378,34 @@ std::string
InputStream::read_all ()
{
std::string str;
while (true) {
size_t n = std::max (size_t (1), m_blen);
const char *b = get (n);
if (b) {
str += std::string (b, n);
} else {
break;
if (mp_inflate) {
// Inflate is special - it does not have a guaranteed byte delivery, so we have to go the
// hard way and pick the file byte by byte
while (true) {
const char *b = get (1);
if (b) {
str += *b;
} else {
break;
}
}
}
} else {
while (true) {
size_t n = std::max (size_t (1), m_blen);
const char *b = get (n);
if (b) {
str += std::string (b, n);
} else {
break;
}
}
}
return str;
}

View File

@ -30,7 +30,7 @@
TEST(1)
{
unsigned char data[] = {
// gzip header
// gzip header:
// 0x1f, 0x8b, 0x08, 0x08,
// 0xed, 0x11, 0x07, 0x50,
// 0x00, 0x03,
@ -39,9 +39,10 @@ TEST(1)
0x56, 0x00, 0xa2, 0x44,
0x85, 0x92, 0xd4, 0xe2,
0x12, 0x85, 0x18, 0x45,
0x2e, 0x00, 0x20, 0xc7,
0x43, 0x6a, 0x12, 0x00,
0x00, 0x00
0x2e, 0x00,
// gzip tail (8 bytes):
// 0x20, 0xc7, 0x43, 0x6a, CRC32
// 0x12, 0x00, 0x00, 0x00 uncompressed file size
};
tl::InputMemoryStream ims ((const char *) data, sizeof (data));

View File

@ -0,0 +1,91 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlUnitTest.h"
#include "tlResources.h"
#include <memory>
// uncompressed resources
TEST(1)
{
unsigned char hw[] = "hello, world!\n";
const char *name;
std::unique_ptr<tl::InputStream> s;
name = "__test_resource1";
tl::resource_id_type id = tl::register_resource (name, false, hw, sizeof (hw));
s.reset (tl::get_resource ("__doesnotexist"));
EXPECT_EQ (s.get () == 0, true);
s.reset (tl::get_resource (name));
EXPECT_EQ (s.get () == 0, false);
if (s) {
std::string data = s->read_all ();
EXPECT_EQ (data.size (), strlen ((const char *) hw) + 1);
EXPECT_EQ (data, std::string ((const char *) hw, sizeof (hw)));
}
tl::unregister_resource (id);
s.reset (tl::get_resource (name));
EXPECT_EQ (s.get () == 0, true);
}
// compressed resources
TEST(2)
{
const unsigned char hw[] = {
0x78,0x9c, // zlib header
// data:
0xcb,0x48,0xcd,0xc9,0xc9,0xd7,0x51,0x28,0xcf,0x2f,0xca,0x49,0x51,0xe4,
0x02,0x00,
0x26,0xb2,0x04,0xb4, // zlib CRC
};
unsigned char hw_decoded[] = "hello, world!\n";
const char *name;
std::unique_ptr<tl::InputStream> s;
name = "__test_resource2";
tl::resource_id_type id = tl::register_resource (name, true, hw, sizeof (hw));
s.reset (tl::get_resource ("__doesnotexist"));
EXPECT_EQ (s.get () == 0, true);
s.reset (tl::get_resource (name));
EXPECT_EQ (s.get () == 0, false);
if (s) {
std::string data = s->read_all ();
EXPECT_EQ (data.size (), strlen ((const char *) hw_decoded));
EXPECT_EQ (data, std::string ((const char *) hw_decoded, sizeof (hw_decoded) - 1));
}
tl::unregister_resource (id);
s.reset (tl::get_resource (name));
EXPECT_EQ (s.get () == 0, true);
}

View File

@ -27,6 +27,7 @@ SOURCES = \
tlLongIntTests.cc \
tlMathTests.cc \
tlObjectTests.cc \
tlResourcesTests.cc \
tlReuseVectorTests.cc \
tlStableVectorTests.cc \
tlStreamTests.cc \