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;
+}