From 81e55e89017658fd2d82fea9dd01adc3ee684dee Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 26 Mar 2018 17:16:47 +0200 Subject: [PATCH] gems: TrueType VFS plugin This commit introduces a VFS plugin that exposes the glyphs and metadata of a TrueType font as a pseudo file system. The TTF font data is obtained from the VFS. The resulting pseudo file system is a directory that contains the files 'glyphs', 'baseline', 'max_width', and 'max_height'. The counter part of the plugin is the 'Vfs_font' class that implements the 'Text_painter::Font' interface by accessing the pseudo file system as provided by the TTF VFS plugin. Fixes #2740 --- repos/gems/include/gems/cached_font.h | 270 ++++++++++++++++++ repos/gems/include/gems/vfs_font.h | 169 +++++++++++ repos/gems/lib/mk/vfs_ttf.mk | 9 + repos/gems/recipes/src/vfs_ttf/content.mk | 16 ++ repos/gems/recipes/src/vfs_ttf/hash | 1 + repos/gems/recipes/src/vfs_ttf/used_apis | 7 + .../gems/src/lib/vfs/ttf/glyphs_file_system.h | 154 ++++++++++ repos/gems/src/lib/vfs/ttf/target.mk | 2 + repos/gems/src/lib/vfs/ttf/vfs.cc | 173 +++++++++++ 9 files changed, 801 insertions(+) create mode 100644 repos/gems/include/gems/cached_font.h create mode 100644 repos/gems/include/gems/vfs_font.h create mode 100644 repos/gems/lib/mk/vfs_ttf.mk create mode 100644 repos/gems/recipes/src/vfs_ttf/content.mk create mode 100644 repos/gems/recipes/src/vfs_ttf/hash create mode 100644 repos/gems/recipes/src/vfs_ttf/used_apis create mode 100644 repos/gems/src/lib/vfs/ttf/glyphs_file_system.h create mode 100644 repos/gems/src/lib/vfs/ttf/target.mk create mode 100644 repos/gems/src/lib/vfs/ttf/vfs.cc diff --git a/repos/gems/include/gems/cached_font.h b/repos/gems/include/gems/cached_font.h new file mode 100644 index 0000000000..9bfb150862 --- /dev/null +++ b/repos/gems/include/gems/cached_font.h @@ -0,0 +1,270 @@ +/* + * \brief Glyph cache + * \author Norman Feske + * \date 2018-03-27 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__GEMS__CACHED_FONT_T_ +#define _INCLUDE__GEMS__CACHED_FONT_T_ + +#include +#include +#include + +namespace Genode { class Cached_font; } + + +class Genode::Cached_font : public Text_painter::Font +{ + public: + + struct Stats + { + unsigned misses; + unsigned hits; + unsigned consumed_bytes; + + void print(Output &out) const + { + Genode::print(out, "used: ", consumed_bytes/1024, " KiB, " + "hits: ", hits, ", misses: ", misses); + } + }; + + private: + + typedef Text_painter::Area Area; + typedef Text_painter::Font Font; + typedef Text_painter::Glyph Glyph; + + struct Time { unsigned value; }; + + Allocator &_alloc; + Font const &_font; + size_t const _limit; + Time mutable _now { 0 }; + Stats mutable _stats { }; + + class Cached_glyph : Avl_node + { + private: + + friend class Avl_node; + friend class Avl_tree; + friend class Cached_font; + + Codepoint const _codepoint; + Glyph const _glyph; + Time _last_used; + + Glyph::Opacity _values[]; + + bool _higher(Codepoint const other) const + { + return _codepoint.value > other.value; + } + + unsigned _importance(Time now) const + { + return now.value - _last_used.value; + } + + public: + + Cached_glyph(Codepoint c, Glyph const &glyph, Time now) + : + _codepoint(c), + _glyph({ .width = glyph.width, + .height = glyph.height, + .vpos = glyph.vpos, + .advance = glyph.advance, + .values = _values }), + _last_used(now) + { + for (unsigned i = 0; i < glyph.num_values(); i++) + _values[i] = glyph.values[i]; + } + + /** + * Avl_node interface + */ + bool higher(Cached_glyph *c) { return _higher(c->_codepoint); } + + Cached_glyph *find_by_codepoint(Codepoint requested) + { + if (_codepoint.value == requested.value) return this; + + Cached_glyph *c = Avl_node::child(_higher(requested)); + + return c ? c->find_by_codepoint(requested) : nullptr; + } + + Cached_glyph *find_least_recently_used(Time now) + { + Cached_glyph *result = this; + + for (unsigned i = 0; i < 2; i++) { + Cached_glyph *c = Avl_node::child(i); + if (c && c->_importance(now) > result->_importance(now)) + result = c; + } + return result; + } + + void mark_as_used(Time now) { _last_used = now; } + + void apply(Font::Apply_fn const &fn) const { fn.apply(_glyph); } + }; + + Avl_tree mutable _avl_tree { }; + + /** + * Size of one cache entry in bytes + */ + size_t const _alloc_size = sizeof(Cached_glyph) + + 4*_font.bounding_box().count(); + + /** + * Add cache entry for the given glyph + * + * \throw Out_of_ram + * \throw Out_of_caps + */ + void _insert(Codepoint codepoint, Glyph const &glyph) + { + auto const cached_glyph_ptr = (Cached_glyph *)_alloc.alloc(_alloc_size); + + _stats.consumed_bytes += _alloc_size; + + memset(cached_glyph_ptr, 0, _alloc_size); + + construct_at(cached_glyph_ptr, codepoint, glyph, _now); + + _avl_tree.insert(cached_glyph_ptr); + } + + /** + * Evict glyph from cache + */ + void _remove(Cached_glyph &glyph) + { + _avl_tree.remove(&glyph); + + glyph.~Cached_glyph(); + + _alloc.free(&glyph, _alloc_size); + + _stats.consumed_bytes -= _alloc_size; + } + + Cached_glyph *_find_by_codepoint(Codepoint codepoint) + { + if (!_avl_tree.first()) + return nullptr; + + return _avl_tree.first()->find_by_codepoint(codepoint); + } + + /** + * Evice least recently used glyph from cache + * + * \return true if a glyph was released + */ + bool _remove_least_recently_used() + { + if (!_avl_tree.first()) + return false; + + Cached_glyph *glyph = _avl_tree.first()->find_least_recently_used(_now); + if (!glyph) + return false; /* this should never happen */ + + _remove(*glyph); + _stats.misses++; + return true; + } + + void _remove_all() + { + while (Cached_glyph *glyph_ptr = _avl_tree.first()) + _remove(*glyph_ptr); + } + + public: + + struct Limit { size_t value; }; + + /** + * Constructor + * + * \param alloc backing store for cached glyphs + * \param font original (uncached) font + * \param limit maximum cache size in bytes + */ + Cached_font(Allocator &alloc, Font const &font, Limit limit) + : + _alloc(alloc), _font(font), _limit(limit.value) + { } + + ~Cached_font() { _remove_all(); } + + Stats stats() const { return _stats; } + + void _apply_glyph(Codepoint c, Apply_fn const &fn) const override + { + _now.value++; + + /* + * Try to lookup glyph from the cache. If it is missing, fill cache + * with requested glyph and repeat the lookup. When under memory + * pressure, flush least recently used glyphs from cache. + * + * Even though '_apply_glyph' is a const method, the internal cache + * and stats must of course be mutated. Hence the 'const_cast'. + */ + Cached_font &mutable_this = const_cast(*this); + + /* retry once after handling a cache miss */ + for (int i = 0; i < 2; i++) { + + if (Cached_glyph *glyph_ptr = mutable_this._find_by_codepoint(c)) { + glyph_ptr->apply(fn); + glyph_ptr->mark_as_used(_now); + _stats.hits += (i == 0); + return; + } + + while (_stats.consumed_bytes + _alloc_size > _limit) + if (!mutable_this._remove_least_recently_used()) + break; + + _font.apply_glyph(c, [&] (Glyph const &glyph) { + mutable_this._insert(c, glyph); }); + } + } + + Advance_info advance_info(Codepoint c) const override + { + unsigned width = 0; + Text_painter::Fixpoint_number advance { 0 }; + + /* go through the '_apply_glyph' cache-fill mechanism */ + Font::apply_glyph(c, [&] (Glyph const &glyph) { + width = glyph.width, advance = glyph.advance; }); + + return Advance_info { .width = width, .advance = advance }; + } + + unsigned baseline() const override { return _font.baseline(); } + + Area bounding_box() const override { return _font.bounding_box(); } +}; + +#endif /* _INCLUDE__GEMS__CACHED_FONT_T_ */ diff --git a/repos/gems/include/gems/vfs_font.h b/repos/gems/include/gems/vfs_font.h new file mode 100644 index 0000000000..0747262e5b --- /dev/null +++ b/repos/gems/include/gems/vfs_font.h @@ -0,0 +1,169 @@ +/* + * \brief Implementation of 'Text_painter::Font' for VFS-mounted fonts + * \author Norman Feske + * \date 2018-03-26 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__GEMS__VFS_FONT_T_ +#define _INCLUDE__GEMS__VFS_FONT_T_ + +#include +#include + +namespace Genode { class Vfs_font; } + + +class Genode::Vfs_font : public Text_painter::Font +{ + public: + + typedef Glyph_painter::Glyph Glyph; + + static constexpr Vfs::file_size GLYPH_SLOT_BYTES = 64*1024; + + class Glyph_header + { + private: + + uint8_t _width = 0; + uint8_t _height = 0; + uint8_t _vpos = 0; + int8_t _advance_decimal = 0; + uint8_t _advance_fractional = 0; + uint8_t _reserved[3] { }; + + Glyph::Opacity _values[]; + + float _advance() const + { + float value = 256.0*_advance_decimal + _advance_fractional; + return value/256; + } + + public: + + Glyph_header(Glyph const &glyph) + : + _width ((uint8_t)min(255U, glyph.width)), + _height((uint8_t)min(255U, glyph.height)), + _vpos ((uint8_t)min(255U, glyph.vpos)), + _advance_decimal((int8_t)max(-127, min(127, glyph.advance.decimal()))), + _advance_fractional((uint8_t)glyph.advance.value & 0xff) + { } + + Glyph_header() { } + + Glyph glyph() const { return Glyph { .width = _width, + .height = _height, + .vpos = _vpos, + .advance = _advance(), + .values = _values }; } + + } __attribute__((packed)); + + private: + + typedef Text_painter::Codepoint Codepoint; + typedef Text_painter::Area Area; + typedef Directory::Path Path; + + Directory const _font_dir; + unsigned const _baseline; + Area const _bounding_box; + + struct Glyph_buffer + { + Allocator &_alloc; + + unsigned const num_bytes; + + Glyph_header &header; + + Glyph_buffer(Allocator &alloc, Area size) + : + _alloc(alloc), num_bytes(sizeof(Glyph_header) + size.count()*4), + header(*(Glyph_header *)alloc.alloc(num_bytes)) + { } + + ~Glyph_buffer() { _alloc.free(&header, num_bytes); } + + char *ptr() { return (char *)&header; } + }; + + Glyph_buffer mutable _buffer; + + Readonly_file _glyphs_file; + + template + static T _value_from_file(Directory const &dir, Path const &path, + T const &default_value) + { + T result = default_value; + try { + Readonly_file const file(dir, path); + char buf[MAX_LEN + 1] { }; + if (file.read(buf, sizeof(buf)) <= MAX_LEN) + if (ascii_to(buf, result)) + return result; + } + catch (Readonly_file::Open_failed) { } + return default_value; + } + + static Readonly_file::At _file_pos(Codepoint c) + { + return Readonly_file::At{(Vfs::file_size)c.value*GLYPH_SLOT_BYTES}; + } + + public: + + struct Unavailable : Exception { }; + + /** + * Constructor + * + * \param alloc allocator for glyph buffer + * \param dir directory + * \param path path to font + * + * \throw Unavailable unable to obtain font data + */ + Vfs_font(Allocator &alloc, Directory const &dir, Path const &path) + : + _font_dir(dir, path), + _baseline(_value_from_file(_font_dir, "baseline", 0U)), + _bounding_box(_value_from_file(_font_dir, "max_width", 0U), + _value_from_file(_font_dir, "max_height", 0U)), + _buffer(alloc, _bounding_box), + _glyphs_file(_font_dir, "glyphs") + { } + + void _apply_glyph(Codepoint c, Apply_fn const &fn) const override + { + _glyphs_file.read(_file_pos(c), _buffer.ptr(), _buffer.num_bytes); + + fn.apply(_buffer.header.glyph()); + } + + Advance_info advance_info(Codepoint c) const override + { + _glyphs_file.read(_file_pos(c), _buffer.ptr(), sizeof(Glyph_header)); + + Glyph const glyph = _buffer.header.glyph(); + + return Advance_info { .width = glyph.width, .advance = glyph.advance }; + } + + unsigned baseline() const override { return _baseline; } + + Area bounding_box() const override { return _bounding_box; } +}; + +#endif /* _INCLUDE__GEMS__TTF_FONT_T_ */ diff --git a/repos/gems/lib/mk/vfs_ttf.mk b/repos/gems/lib/mk/vfs_ttf.mk new file mode 100644 index 0000000000..da532b95ab --- /dev/null +++ b/repos/gems/lib/mk/vfs_ttf.mk @@ -0,0 +1,9 @@ +SRC_CC = vfs.cc + +INC_DIR += $(REP_DIR)/src/lib/vfs/ttf + +LIBS += ttf_font + +vpath %.cc $(REP_DIR)/src/lib/vfs/ttf + +SHARED_LIB = yes diff --git a/repos/gems/recipes/src/vfs_ttf/content.mk b/repos/gems/recipes/src/vfs_ttf/content.mk new file mode 100644 index 0000000000..1db305b192 --- /dev/null +++ b/repos/gems/recipes/src/vfs_ttf/content.mk @@ -0,0 +1,16 @@ +MIRROR_FROM_REP_DIR := lib/mk/vfs_ttf.mk lib/mk/ttf_font.mk \ + src/lib/vfs/ttf src/lib/ttf_font + +STB_PORT_DIR := $(call port_dir,$(GENODE_DIR)/repos/libports/ports/stb) + +content: $(MIRROR_FROM_REP_DIR) include/stb_truetype.h LICENSE + +include/stb_truetype.h: + mkdir -p $(dir $@) + cp -r $(STB_PORT_DIR)/include/stb_truetype.h $@ + +$(MIRROR_FROM_REP_DIR): + $(mirror_from_rep_dir) + +LICENSE: + cp $(GENODE_DIR)/LICENSE $@ diff --git a/repos/gems/recipes/src/vfs_ttf/hash b/repos/gems/recipes/src/vfs_ttf/hash new file mode 100644 index 0000000000..69ef7e3f61 --- /dev/null +++ b/repos/gems/recipes/src/vfs_ttf/hash @@ -0,0 +1 @@ +2018-03-30 89b39a8703dbb8b098068d2ec4f73b4705debb44 diff --git a/repos/gems/recipes/src/vfs_ttf/used_apis b/repos/gems/recipes/src/vfs_ttf/used_apis new file mode 100644 index 0000000000..6e8f81f415 --- /dev/null +++ b/repos/gems/recipes/src/vfs_ttf/used_apis @@ -0,0 +1,7 @@ +base +os +so +libc +vfs +gems +nitpicker_gfx diff --git a/repos/gems/src/lib/vfs/ttf/glyphs_file_system.h b/repos/gems/src/lib/vfs/ttf/glyphs_file_system.h new file mode 100644 index 0000000000..a6e99ac397 --- /dev/null +++ b/repos/gems/src/lib/vfs/ttf/glyphs_file_system.h @@ -0,0 +1,154 @@ +/* + * \brief Glyphs file system + * \author Norman Feske + * \date 2018-03-26 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _GLYPHS_FILE_SYSTEM_H_ +#define _GLYPHS_FILE_SYSTEM_H_ + +/* Genode includes */ +#include +#include + +/* gems includes */ +#include + +namespace Vfs { + + using namespace Genode; + + class Glyphs_file_system; + + typedef Text_painter::Font Font; + typedef Vfs_font::Glyph_header Glyph_header; +} + + +class Vfs::Glyphs_file_system : public Vfs::Single_file_system +{ + private: + + static constexpr unsigned UNICODE_MAX = 0x10ffff; + + static constexpr file_size FILE_SIZE = Vfs_font::GLYPH_SLOT_BYTES*(UNICODE_MAX + 1); + + Font const &_font; + + struct Vfs_handle : Single_vfs_handle + { + Font const &_font; + + Vfs_handle(Directory_service &ds, + File_io_service &fs, + Allocator &alloc, + Font const &font) + : + Single_vfs_handle(ds, fs, alloc, 0), _font(font) + { } + + Read_result read(char *dst, file_size count, + file_size &out_count) override + { + out_count = 0; + + if (seek() > FILE_SIZE) + return READ_ERR_INVALID; + + Codepoint const codepoint { (uint32_t)(seek() / Vfs_font::GLYPH_SLOT_BYTES) }; + + file_size byte_offset = seek() % Vfs_font::GLYPH_SLOT_BYTES; + + _font.apply_glyph(codepoint, [&] (Glyph_painter::Glyph const &glyph) { + + if (byte_offset < sizeof(Glyph_header)) { + + Glyph_header const header(glyph); + + char const * const src = (char const *)&header + byte_offset; + size_t const len = min(sizeof(header) - byte_offset, count); + memcpy(dst, src, len); + + dst += len; + byte_offset += len; + count -= len; + out_count += len; + } + + /* + * Given that 'byte_offset' is at least 'sizeof(header)', + * continue working with 'alpha_offset', which is the first + * offset of interest within the array of alpha values. + */ + size_t const alpha_offset = byte_offset - sizeof(Glyph_header); + size_t const alpha_values_len = 4*glyph.width*glyph.height; + + if (alpha_offset < alpha_values_len) { + char const * const src = (char const *)glyph.values + alpha_offset; + size_t const len = min(alpha_values_len - alpha_offset, count); + memcpy(dst, src, len); + out_count += len; + } + }); + + return READ_OK; + } + + Write_result write(char const *, file_size, file_size &) override + { + return WRITE_ERR_IO; + } + + bool read_ready() override { return true; } + }; + + public: + + Glyphs_file_system(Font const &font) + : + Single_file_system(NODE_TYPE_CHAR_DEVICE, type(), Xml_node("")), + _font(font) + { } + + static char const *type_name() { return "glyphs"; } + + char const *type() override { return type_name(); } + + + /********************************* + ** Directory-service interface ** + *********************************/ + + Open_result open(char const *path, unsigned, + Vfs::Vfs_handle **out_handle, + Allocator &alloc) override + { + if (!_single_file(path)) + return OPEN_ERR_UNACCESSIBLE; + + try { + *out_handle = new (alloc) + Vfs_handle(*this, *this, alloc, _font); + return OPEN_OK; + } + catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } + catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } + } + + Stat_result stat(char const *path, Stat &out) override + { + Stat_result result = Single_file_system::stat(path, out); + out.mode |= 0444; + out.size = FILE_SIZE; + return result; + } +}; + +#endif /* _GLYPHS_FILE_SYSTEM_H_ */ diff --git a/repos/gems/src/lib/vfs/ttf/target.mk b/repos/gems/src/lib/vfs/ttf/target.mk new file mode 100644 index 0000000000..275ef29f2d --- /dev/null +++ b/repos/gems/src/lib/vfs/ttf/target.mk @@ -0,0 +1,2 @@ +TARGET = dummy-vfs_ttf +LIBS = vfs_ttf diff --git a/repos/gems/src/lib/vfs/ttf/vfs.cc b/repos/gems/src/lib/vfs/ttf/vfs.cc new file mode 100644 index 0000000000..7872c0e521 --- /dev/null +++ b/repos/gems/src/lib/vfs/ttf/vfs.cc @@ -0,0 +1,173 @@ +/* + * \brief Truetype font file system + * \author Norman Feske + * \date 2018-03-07 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include + +/* gems includes */ +#include +#include +#include + +/* local includes */ +#include + +namespace Vfs_ttf { + + using namespace Vfs; + using namespace Genode; + + class Font_from_file; + class Local_factory; + class File_system; + + struct Dummy_io_response_handler : Vfs::Io_response_handler + { + void handle_io_response(Vfs::Vfs_handle::Context *) override { }; + }; + + typedef Text_painter::Font Font; +} + + +struct Vfs_ttf::Font_from_file +{ + typedef Directory::Path Path; + + Directory const _dir; + File_content const _content; + + Constructible _font { }; + + /* + * Each slot of the glyphs file is 64 KiB, which limits the maximum glyph + * size to 128x128. We cap the size at 100px to prevent cut-off glyphs. + */ + static constexpr float MAX_SIZE_PX = 100.0; + + Font_from_file(Vfs::File_system &root, Entrypoint &ep, Allocator &alloc, + Path const &file_path, float px) + : + _dir(root, ep, alloc), + _content(alloc, _dir, file_path, File_content::Limit{10*1024*1024}) + { + _content.bytes([&] (char const *ptr, size_t) { + _font.construct(alloc, ptr, min(px, MAX_SIZE_PX)); }); + } + + Font const &font() const { return *_font; } +}; + + +struct Vfs_ttf::Local_factory : File_system_factory +{ + Font_from_file _font; + Cached_font::Limit _cache_limit; + Cached_font _cached_font; + Glyphs_file_system _glyphs_fs; + Readonly_value_file_system _baseline_fs; + Readonly_value_file_system _max_width_fs; + Readonly_value_file_system _max_height_fs; + + Local_factory(Env &env, Allocator &alloc, Xml_node node, + Vfs::File_system &root_dir) + : + _font(root_dir, env.ep(), alloc, + node.attribute_value("path", Directory::Path()), + node.attribute_value("size_px", 16.0)), + _cache_limit({node.attribute_value("cache", Number_of_bytes())}), + _cached_font(alloc, _font.font(), _cache_limit), + _glyphs_fs (_cached_font), + _baseline_fs ("baseline", _font.font().baseline()), + _max_width_fs ("max_width", _font.font().bounding_box().w()), + _max_height_fs("max_height", _font.font().bounding_box().h()) + { } + + Vfs::File_system *create(Env &, Allocator &, Xml_node node, + Io_response_handler &, Vfs::File_system &) override + { + if (node.has_type(Glyphs_file_system::type_name())) + return &_glyphs_fs; + + if (node.has_type(Readonly_value_file_system::type_name())) + return _baseline_fs.matches(node) ? &_baseline_fs + : _max_width_fs.matches(node) ? &_max_width_fs + : _max_height_fs.matches(node) ? &_max_height_fs + : nullptr; + + return nullptr; + } +}; + + +class Vfs_ttf::File_system : private Local_factory, + private Dummy_io_response_handler, + public Vfs::Dir_file_system +{ + private: + + typedef String<200> Config; + static Config _config(Xml_node node) + { + char buf[Config::capacity()] { }; + + Xml_generator xml(buf, sizeof(buf), "dir", [&] () { + typedef String<64> Name; + xml.attribute("name", node.attribute_value("name", Name())); + xml.node("glyphs", [&] () { }); + xml.node("readonly_value", [&] () { xml.attribute("name", "baseline"); }); + xml.node("readonly_value", [&] () { xml.attribute("name", "max_width"); }); + xml.node("readonly_value", [&] () { xml.attribute("name", "max_height"); }); + }); + return Config(Cstring(buf)); + } + + public: + + File_system(Env &env, Allocator &alloc, Xml_node node, + Vfs::File_system &root_dir) + : + Local_factory(env, alloc, node, root_dir), + Vfs::Dir_file_system(env, alloc, + Xml_node(_config(node).string()), + *this, *this, root_dir) + { } + + char const *type() override { return "ttf"; } +}; + + +/************************** + ** VFS plugin interface ** + **************************/ + +extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) +{ + struct Factory : Vfs::File_system_factory + { + Vfs::File_system *create(Genode::Env &env, Genode::Allocator &alloc, + Genode::Xml_node node, + Vfs::Io_response_handler &, + Vfs::File_system &root_dir) override + { + try { + return new (alloc) Vfs_ttf::File_system(env, alloc, node, root_dir); } + catch (...) { } + return nullptr; + } + }; + + static Factory factory; + return &factory; +}